[alt.sources] multiple host command launcher

merlyn@iwarp.intel.com (Randal Schwartz) (03/08/90)

Here's the 'gsh' I've been using for a while (industrial strength by
now).  The coding style is not pretty, but it has been roadtested.

Yes, this stuff was inspired by the 'gsh' in the Perl distribution,
although I've taken it about three steps further.  Mine has parallel
launching and waiting, a built-in (but overridable/extensible)
hostlist, and a timeout for those rsh's that launch but "never" come
back.  You'll want to edit the builtin hostlist, unless you just
*happen* to have a bunch of systems named 'iwarpa', 'iwarpb', etc.
etc. :-)

Enjoy.

================================================== snip here
#!/local/merlyn/bin/perl
## Copyright (C) 1989, 1990, by Randal L. Schwartz.  All Rights Reserved.
## usage: gsh [options] hostspec [command [arg]...]
## Runs command and args on hosts according to hostspec.  Results are
## sent to STDOUT, with hostname prefix.  A missing command means to just
## echo the computed hostnames on STDOUT. 'hostspec' is one of:
##   hostname, hostattribute, hostspec+hostspec, hostspec-hostspec
## Default hostlist is defined in @HOSTLIST later on.
##
## options:
## -d: don't run any commands on other hosts... but fork anyway.
## -h hostlist: extend the hostlist with the contents of the named file.
## -H hostlist: replace the hostlist with the contents of the named file.
## -i: give STDIN to the processes as STDIN
## -o place: send the outputs to "place$host" instead of STDOUT
## -n procs: run this many processes at a time (default 5).
##           (remember that each rsh is two processes on this host!)
## -v: be noisy about starting and finishing processes.
## -z sec: zap processes after sec seconds (default 300).

## requires 3.0 beta or better
@HOSTLIST = split(/\n/, <<'ENDHOSTLIST');  # comments allowed in here...
all=vax+sun
sun=sun3+sun4+sun386
sun3=sun3server+sun3client
sun4=sun4server+sun4client
sunserver=sun3server+sun4server
sunclient=sun3client+sun4client
sun3server=sun3/160s+sun3/260s+sun3/280s
sun3client=sun3/50c+sun3/60c+sun3/75c+sun3/140c
sun4server=sun4/280s
sun4client=sun4/110c
sun386=sun386i
iwarpa iwa a vax ultrix2
iwarpb iwb b vax ultrix2
iwarpc iwc c vax ultrix2
iwarpd iwd d vax ultrix2
iwarpe iwe e vax ultrix2
iwarpf iwf f vax ultrix2
iwarpg iwg g vax ultrix2
iwarph iwh h vax ultrix2
iwarpi iwi i vax ultrix2
iwarpj iwj j sun3/160s sunos4 diskserver
iwarpj0 iwj0 j0 sun3/75c sunos4 diskclient
iwarpj1 iwj1 j1 sun3/75c sunos4 diskclient
iwarpj2 iwj2 j2 sun3/75c sunos4 diskclient
iwarpj3 iwj3 j3 sun3/75c sunos4 diskclient
iwarpk iwk k sun3/260s sunos4 diskserver
iwarpk0 iwk0 k0 sun3/75c sunos4 diskclient
iwarpk1 iwk1 k1 sun3/75c sunos4 diskclient
iwarpk2 iwk2 k2 sun3/75c sunos4 diskclient
iwarpk3 iwk3 k3 sun3/75c sunos4 diskclient
iwarpl iwl l sun3/260s sunos4 diskserver
iwarpl0 iwl0 l0 sun3/75c sunos4 diskclient
iwarpl1 iwl1 l1 sun3/75c sunos4 diskclient
iwarpl2 iwl2 l2 sun3/75c sunos4 diskclient
iwarpl3 iwl3 l3 sun3/75c sunos4 diskclient
iwarpm iwm m sun3/260s sunos4 diskserver
iwarpm0 iwm0 m0 sun3/140c sunos4 diskclient
iwarpm1 iwm1 m1 sun3/140c sunos4 diskclient
iwarpm2 iwm2 m2 sun3/140c sunos4 diskclient
iwarpm3 iwm3 m3 sun3/140c sunos4 diskclient
iwarpn iwn n sun3/260s sunos4 diskserver
iwarpn0 iwn0 n0 sun3/140c sunos4 diskclient
iwarpn1 iwn1 n1 sun3/140c sunos4 diskclient
iwarpn2 iwn2 n2 sun3/140c sunos4 diskclient
## iwarpn3 iwn3 n3 sun3/140c sunos4 diskclient
iwarpo iwo o sun3/260s sunos4 diskserver
iwarpo0 iwo0 o0 sun3/140c sunos4 diskclient
iwarpo1 iwo1 o1 sun3/140c sunos4 diskclient
iwarpo2 iwo2 o2 sun3/140c sunos4 diskclient
iwarpo3 iwo3 o3 sun3/140c sunos4 diskclient
iwarpp iwp p sun3/280s sunos4
iwarpp0 iwp0 p0 sun386i sunos4
iwarpp1 iwp1 p1 sun386i sunos4
iwarpp2 iwp2 p2 sun386i sunos4
iwarpp3 iwp3 p3 sun386i sunos4
iwarpp4 iwp4 p4 sun386i sunos4
iwarpp5 iwp5 p5 sun386i sunos4
iwarpq iwq q sun4/280s sunos4 diskserver
## iwarpq0 iwq0 q0 sun4/110c sunos4 diskclient
## iwarpq1 iwq1 q1 sun4/110c sunos4 diskclient
iwarpr iwr r sun3/280s sunos4 diskserver
iwarpr0 iwr0 r0 sun3/60c sunos4 diskclient
iwarpr1 iwr1 r1 sun3/60c sunos4 diskclient
iwarpr2 iwr2 r2 sun3/60c sunos4 diskclient
iwarpr3 iwr3 r3 sun3/60c sunos4 diskclient
iwarpr4 iwr4 r4 sun3/60c sunos4 diskclient
## iwarps iws s sun3/160s sunos4
iwarpv iwv v vax ultrix2
iwarpw iww w vax ultrix2
iwarpx iwx x vax ultrix2
iwarpy iwy y vax ultrix2
iwarpz iwz z sun3/260s sunos4 diskserver
iwarpz0 iwz0 z0 sun3/60c sunos4 diskclient
iwarpz1 iwz1 z1 sun3/60c sunos4 diskclient
iwarpz2 iwz2 z2 sun3/60c sunos4 diskclient
iwarpz3 iwz3 z3 sun3/60c sunos4 diskclient
ENDHOSTLIST

$| = 1; # don't buffer STDOUT

$the_task_filename = "/tmp/$$.thetask";

$tasks = 0;
$taskmax = 5;
$zapsecs = 300;

sub start {
	local($host) = @_;

	print "starting '$host'...\n" if $verbose;
	
	while ($tasks > 0 && $tasks >= $taskmax) {
		&finish();
	};
	unless ($pid = fork) {	# child
		open(STDIN, "<$the_task_filename") ||
			die "Cannot open $the_task_filename as STDIN ($!)";
		open(STDOUT, ">$place$host") ||
			die "Cannot open $place$host ($!)";
		open(STDERR, ">&STDOUT");
		exec 'cat' if $debug;
		$parent = $$;
		if (fork) { # still the child
			exec 'rsh', $host, '/bin/sh';
			die "Cannot exec rsh ($!)";
		}
		# child child
		$zaptime = time + $zapsecs;
		while (time < $zaptime) {
			sleep 5;
			exit 0 if getppid == 1;
		}
		kill 9, $parent;
		print "\nTIMED OUT AFTER $zapsecs SECONDS\n";
		exit 0;
	}
	$tasklist{$pid} = $host;
	$tasks++;
}

sub finish {
	return unless $tasks > 0;
	print "waiting on '", join(" ", sort values(tasklist)), "'...\n"
		if $verbose;
	do {
		die "Nothing to wait for??? ($!)" unless ($pid = wait) > 0;
	} until $tasklist{$pid};
	print "finished task on '", delete $tasklist{$pid}, "'.\n"
		if $verbose;
	$tasks--;
}

sub finishall {
	while ($tasks > 0) {
		&finish();
	}
}

sub gethostlist {
	local($f,$replace) = @_;
	open(GETHOSTLIST, "<$f") || die "Cannot open '$f' ($!)";
	@HOSTLIST = () if $replace;
	unshift(@HOSTLIST, <GETHOSTLIST>); # put it at the beginning
	close(GETHOSTLIST);
}

# end initialization... begin code...

while ($ARGV[0] =~ /^-/) {
	$_ = shift;
	$debug++, $verbose++, next if /^-d/;
	$verbose++, next if /^-v/;
	$taskmax = $1, next if /^-n(.+)/;
	$taskmax = shift, next if /^-n/;
	&gethostlist($1, 1), next if /^-H(.+)/;
	&gethostlist(shift, 1), next if /^-H/;
	&gethostlist($1), next if /^-h(.+)/;
	&gethostlist(shift), next if /^-h/;
	$do_stdin++, next if /^-i/;
	$place = $1, next if /^-o(.+)/;
	$place = shift, next if /^-o/;
	$zapsecs = $1, next if /^-z(.+)/;
	$zapsecs = shift, next if /^-z/;
	die "unknown flag $_";
}

$place = "/tmp/$$.", $do_stdout++ unless $place;

unshift(@HOSTLIST,"TARGET=" . shift);

$the_task .= join(" ", @ARGV);
if ($do_stdin) {
	$_ = join("",<STDIN>);
	chop if /\n$/;
	$the_task = "($the_task ;) <<'FoObAr'\n$_\nFoObAr\n";
	# if I got tricky, I could skip the extra shell, but, hey... it works
}

@TARGETS = ();

$attr{'TARGET'} = 1;	# this is what I want.

for $_ (@HOSTLIST) {
	s/\s*\n?$//;	# toss trailing white
	s/^\s*//;	# toss leading white
	next if /^(#.*)?$/; # skip comment lines and blank lines
	if (/^([^-+=]+)=(.*)/) {
		($name,$repl) = ($1,"+$2");
		next unless $yes = $attr{$name}; # +1 if wanted, -1 if not
		while ($repl =~ s/^([+-])([^-+]+)//) {
			next if $attr{$2};
			$attr{$2} = ($1 eq '-') ? - $yes : $yes;
			print "assigning $attr{$2} to $2\n" if $debug;
		}
	} else {	# must be a terminal node:
		@attr = split;
		$host = $attr[0];
		$wanted = 0;
		for $attr (@attr) {
			$wanted++, next if $attr{$attr} > 0;
			$wanted=-1, last if $attr{$attr} < 0;
		}
		push(TARGETS, $host) if $wanted > 0;
	}
}

if ($the_task =~ /^\s*$/) { # no command?  just list the hosts
	print join("\n", @TARGETS), "\n";
	exit 0;
}

open(THE_TASK, ">$the_task_filename") || die "Cannot open THE_TASK ($!)";
print THE_TASK $the_task;
close(THE_TASK);

for $host (@TARGETS) {	# launch'em all, $taskmax at a time
	&start($host);
}

&finishall();		# and hang out while the last $taskmax finish

unlink $the_task_filename; # no need for this anymore

exit 0 unless $do_stdout;

for $host (@TARGETS) {	# show what they said
	open(F,"<$place$host") || die "missing output for $host ($!)";
	if ($_ = join("$host:\t", <F>)) {
		print "$host:\t$_";
		print "\n" unless /\n$/;
	}
	close(F);
	unlink "$place$host";
}
exit 0;
================================================== snip here

Just another Perl hacker,
-- 
/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ==========\
| on contract to Intel's iWarp project, Beaverton, Oregon, USA, Sol III      |
| merlyn@iwarp.intel.com ...!any-MX-mailer-like-uunet!iwarp.intel.com!merlyn |
\=Cute Quote: "Welcome to Portland, Oregon, home of the California Raisins!"=/