frisbeelauncher.in 6.65 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2 3 4 5 6 7 8

#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2002 University of Utah and the Flux Group.
# All rights reserved.
#

Robert Ricci's avatar
Robert Ricci committed
9 10 11
use Getopt::Std;
use POSIX 'setsid'; # For &daemonize
use Sys::Syslog;
12
use English;
Robert Ricci's avatar
Robert Ricci committed
13 14 15

# Configure variables
my $TB		= "@prefix@";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
16 17
my $TBOPS	= "@TBOPSEMAIL@";

18 19 20 21 22 23
#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

Leigh B. Stoller's avatar
Leigh B. Stoller committed
24 25 26 27
#
# Turn off line buffering on output
#
$| = 1;
Robert Ricci's avatar
Robert Ricci committed
28 29 30

use lib "@prefix@/lib";
use libdb;
31
use libtestbed;
Robert Ricci's avatar
Robert Ricci committed
32 33 34 35

# Defines
my $FRISBEED	= "$TB/sbin/frisbeed";
my $BASEADDR	= "234.5.6";
36
my $BASEPORT	= "3564";
Robert Ricci's avatar
Robert Ricci committed
37 38 39
my $LOGFILE	= "$TB/log/frisbeelauncher";

# Process command line options
Leigh B. Stoller's avatar
Leigh B. Stoller committed
40
getopts('d',\%opt);
Robert Ricci's avatar
Robert Ricci committed
41 42 43 44 45
if (@ARGV != 1) {
	exit &usage();
}
$imageid = shift @ARGV;

46 47 48
#
# Untaint the argument.
#
49
if ($imageid =~ /^([-\@\w\+.]+)$/) {
50 51 52 53 54 55
	$imageid = $1;
} else {
	die("Invalid image '$imageid' contains illegal characters.\n");
}


Leigh B. Stoller's avatar
Leigh B. Stoller committed
56 57 58 59
# Grab the filename to give to frisbee
my $filename = &get_filename($imageid);

# Make sure that the user can read the image file or bomb out right now.
60
if (! -R $filename) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
61 62 63 64 65 66 67 68 69 70
	die("You do not have permission to read the image file for\n".
	    "imageid $imageid: $filename\n");
}

#
# Need to lock the tables here, since we are going to mess with the
# busy indicator.
#
&lock_tables;

Robert Ricci's avatar
Robert Ricci committed
71 72 73
# Try to discover if some other process is handling this address
$address = &get_address($imageid);

Leigh B. Stoller's avatar
Leigh B. Stoller committed
74 75 76
if ($address && &keepbusy($imageid)) {
        &unlock_tables;
	&debug("A server ($address) is already running for image $imageid\n");
Robert Ricci's avatar
Robert Ricci committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
	exit (0);
}

# Pick an address: Die if unsucessful, set address and unlock if sucessful
$address = &pick_address;
&debug("Picked address $address\n");

if (!$address) {
	&unlock_tables;
	die "Unable to find a free address to send on\n";
}

&set_address($imageid,$address);
&unlock_tables;

# Run in the background
93 94 95
if (TBBackGround($LOGFILE)) {
    exit(0);
}
Robert Ricci's avatar
Robert Ricci committed
96

97 98 99 100 101 102 103
#
# Drop root permissions, if we have them
#
if ($EUID == 0) {
	$EUID = $UID;
}

Robert Ricci's avatar
Robert Ricci committed
104 105 106 107 108 109
# Set up a signal handler that will clean up in case we get killed
$SIG{HUP} = $SIG{INT} = $SIG{TERM} = \&cleanup;
# XXX: Any others we should catch?

# Now, we actually launch Frisbee
while (1) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
110 111 112 113 114 115 116 117 118 119 120
        #
        # Each time the server exits, test the busy bit to see if
	# it should keep going. This has to be done with tables locked
	# since another caller is going to bump it.
	#
	&lock_tables();
	if (! &testbusy($imageid)) {
		last;
	}
	&unlock_tables();
	
Robert Ricci's avatar
Robert Ricci committed
121 122
	if ($child_pid = fork()) {
		# Wait for child to exit
Leigh B. Stoller's avatar
Leigh B. Stoller committed
123 124 125
		waitpid($child_pid, 0);

		if ($?) {
126
			SENDMAIL($TBOPS, "Frisbeed Failed!",
Leigh B. Stoller's avatar
Leigh B. Stoller committed
127 128 129 130 131 132 133 134 135 136 137
				 "Imageid: $imageid\n".
				 "Address: $address\n\n".
				 "Process $child_pid exited with value $?.\n".
				 "Please look at the syslog for frisbeed!\n\n".
				 "NOTE: Another frisbeed will not start!\n");
			#
			# Dump early. This will leave the address in
			# in the DB, so that another one will not start
			# until the matter is resolved by someone. 
			# 
			exit(1);
Robert Ricci's avatar
Robert Ricci committed
138
		}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
139 140
	}
	else {
Robert Ricci's avatar
Robert Ricci committed
141
		# Child branch
Leigh B. Stoller's avatar
Leigh B. Stoller committed
142 143 144 145 146 147 148 149 150 151
		# The database format for address is host:port - however,
		# we need to give them as seperate arguments to frisbeed.

		if ($address =~ /(.*):(.*)/) {
			my $addr = $1;
			my $port = $2;

			if (!exec("$FRISBEED -m $addr -p $port $filename")) {
				die("$$: Unable to exec $FRISBEED\n");
			}
Robert Ricci's avatar
Robert Ricci committed
152
		}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
153
		die("$$: Bad address format: $address.\n");
Robert Ricci's avatar
Robert Ricci committed
154 155 156 157
	}
}

&clear_address;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
158
&unlock_tables();
Robert Ricci's avatar
Robert Ricci committed
159 160 161 162 163 164 165 166
exit(0);

######################################################################
# Subroutines
######################################################################

# Print out a usage mesage
sub usage {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
167
	print "Usage: $0 [-d] IMAGEID\n";
Robert Ricci's avatar
Robert Ricci committed
168 169 170 171 172 173 174 175 176 177 178 179 180
	print "-d:	Print debugging output\n";
}

# Only print if -d option was given. Also add $$ on the beginning of the
# string, to ease debugging
sub debug {
	if ($opt{d}) { print "$$: ", @_ };
}

# Grab the address for the passed-in imageid
sub get_address {
	my ($imageid) = @_;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
181 182 183
	my $sth =
	    DBQueryFatal("SELECT imageid,load_address ".
			 "FROM images WHERE imageid='$imageid'");
Robert Ricci's avatar
Robert Ricci committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

	my @row = $sth->fetchrow;
	if (!@row) {
		die "No such imageid: $imageid\n";
	}

	return $row[1];
}

# Grab the filename for the passed-in imageid
sub get_filename {
	my ($imageid) = @_;
	my $image_query = "SELECT path FROM images WHERE " .
		"imageid='$imageid'";

	my $sth = DBQueryFatal($image_query);

	my @row = $sth->fetchrow;
	if (!@row) {
		die "No such imageid: $imageid\n";
	}

	return $row[0];
}


# Lock the tables used in this script - waits indefinitely until it
# succeeds
sub lock_tables {
	while (1) {
		&debug("Locking tables\n");
		my $sth = DBQuery("LOCK TABLES images WRITE"); 
		if (!$sth) {
			print "DB Error locking tables. Waiting a bit ...\n";
			sleep(10);
		} else {
			last;
		}
	}
}

# Unlock the tables used in this script
sub unlock_tables {
	&debug("Unlocking tables\n");
	DBQueryFatal("UNLOCK TABLES"); 
}

# Pick out an address to use
sub pick_address {
	my $address_query = "SELECT load_address FROM images WHERE " .
		"load_address IS NOT NULL and load_address != ''";
	my $sth = DBQueryFatal($address_query);

	my %used_addrs = (); # Loading addresses already taken
	while (@row = $sth->fetchrow) {
239 240 241 242
		$row[0] =~ /^$BASEADDR\.(\d+):(\d+)$/;
		# $1 is the address, $2 the port number
		if ($1 && $2) {
			$used_addrs{$1} = $2;
Robert Ricci's avatar
Robert Ricci committed
243 244 245 246 247 248
		}
	}

	my $address;
	for (my $i = 1; $i < 255; $i++) {
		if (!$used_addrs{$i}) {
249 250
			my $port = $BASEPORT + ($i - 1);
			$address = "${BASEADDR}.${i}:${port}";
Robert Ricci's avatar
Robert Ricci committed
251 252 253 254 255 256 257 258 259 260
			last;
		}
	}

	return $address;
}

# Pass in an imageid, and an address
sub set_address {
	my ($imageid,$address) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

	DBQueryFatal("UPDATE images SET load_address='$address',load_busy=1 " .
		     "WHERE imageid='$imageid'");
}

# Bump the busy indicator to keep the frisbeed going.
sub keepbusy($imageid) {
	my ($imageid) = @_;

	DBQueryFatal("UPDATE images SET load_busy=GREATEST(load_busy,1) " .
		     "WHERE imageid='$imageid'");

	return 1;
}

# Test the busy indicator, and set to zero.
sub testbusy($imageid) {
	my ($imageid) = @_;

	my $query_result =
	    DBQueryFatal("select load_busy from images ".
			 "WHERE imageid='$imageid'");

	my @row = $query_result->fetchrow;
	if (!@row) {
	        return 0;
	}
	
	if ($row[0]) {
	    DBQueryFatal("UPDATE images SET load_busy=0 ".
			 "WHERE imageid='$imageid'");
	}
	return $row[0];
Robert Ricci's avatar
Robert Ricci committed
294 295 296 297 298 299 300 301 302 303 304 305 306
}

# Kill off our child process, if started, and clear out registered address
# Also, die off
sub cleanup {
	print STDERR "$$: Killed, cleaning up\n";
	if ($child_pid) {
		kill 15, $child_pid;
	}
	&clear_address;
	exit(1);
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
307
# Clear out the address (and pid) registered to this process
Robert Ricci's avatar
Robert Ricci committed
308 309 310
sub clear_address {
	&debug("Clearing out registered load_address\n");
	# Now, clear out the load_address we had set up
Leigh B. Stoller's avatar
Leigh B. Stoller committed
311
	my $address_clear = "UPDATE images SET load_address='',load_busy=0 " .
Robert Ricci's avatar
Robert Ricci committed
312 313 314
		"WHERE imageid='$imageid'";
	DBQueryFatal($address_clear);
}