endexp.in 8.03 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
#!/usr/bin/perl -wT
use English;
use Getopt::Std;

#
# This gets invoked from the Web interface. Terminate an experiment.
# Most of the STDOUT prints are never seen since the web interface
# repeats only errors. My plan is make this script the front end to
# experiment termination and make tbend a backend program that no one
# uses.
#
# The -b (batch) argument is so that this script can be part of a batchmode
# that starts/ends experiments offline. In that case, we don't want to put
# it into the background and send email, but just want an exit status 
# returned to the batch system.
#
sub usage()
{
    print STDOUT "Usage: endexp [-b] <pid> <eid>\n";
    exit(-1);
}
my  $optlist = "b";

#
# Configure variables
#
my $TB     = "@prefix@";
my $DBNAME = "@TBDBNAME@";
my $TBOPS  = "@TBOPSEMAIL@";
30
my $TBLOGS = "@TBLOGSEMAIL@";
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

my $tbdir    = "$TB/bin/";
my $projroot = "/proj";
my $tbdata   = "tbdata";
my $logname  = 0;
my $batch    = 0;

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

#
# Turn off line buffering on output
#
$| = 1;

49
50
51
52
53
54
#
# Testbed Support library
# 
push(@INC, "$TB/lib");
require libtestbed;

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (@ARGV != 2) {
    usage();
}
my $pid   = $ARGV[0];
my $eid   = $ARGV[1];
if (defined($options{"b"})) {
    $batch = $options{"b"};
}

#
# Untaint the arguments.
#
if ($pid =~ /^([-\@\w.]+)$/) {
    $pid = $1;
}
if ($eid =~ /^([-\@\w.]+)$/) {
    $eid = $1;
}

my $piddir  = "$projroot/$pid";
my $expdir  = "$piddir/exp";
my $eiddir  = "$expdir/$eid";

#
# Set up for querying the database.
# 
use Mysql;
my $DB = Mysql->connect("localhost", $DBNAME, "script", "none");

#
# We have to protect against trying to end an experiment that is currently
# in the process of being terminated. We use a timestamp for this purpose.
# If the timestamp is ever non-null, then something is wrong and we should
# not proceed.
#
# We also have to guard against trying to terminate an experiment that
# is still in the process of configuring. Its easiest to force the user
# to wait!
#
$query_result =
    $DB->query("SELECT expt_terminating,expt_ready FROM experiments ".
	       "WHERE eid='$eid' and pid='$pid'");

if (! $query_result) {
    fatal("DB Error getting experiment termination date for $pid/$eid\n");
}
if ($query_result->numrows < 1) {
    print STDOUT "No such experiment $pid/$eid exists!\n";
    exit(1);
}

@row = $query_result->fetchrow_array();
if (defined($row[0])) {
    print STDOUT
	"It appears that $pid/$eid started terminating at $row[0]\n".
	"You will be notified via email when the experiment has been ".
	"torn down\n";
    exit(1);
}
if (! $row[1]) {
    print STDOUT
	"It appears that experiment $pid/$eid is still configuring.\n".
	"The user that created the experiment will be notified via email\n".
	"when it has been fully configured and is ready for use\n";
    exit(1);
}

#
# Get some user information. 
#
$query_result = $DB->query("SELECT uid,usr_name,usr_email,admin from users ".
			   "WHERE unix_uid='$EUID'");

if (! $query_result) {
    fatal("DB Error getting user information for uid $EUID\n");
}
if ($query_result->numrows < 1) {
    print STDOUT "Go Away! You do not exist in the Emulab Database.\n";
    exit(1);
}

@row = $query_result->fetchrow_array();
$uid        = $row[0];
$user_name  = $row[1];
$user_email = $row[2];
$isadmin    = $row[3];

#
# Verify that this person is allowed to end the experiment. Must be
# in the project membership table, or must be an admin type. Note that
# any script down the line has to do an admin check also. 
#
if (! $isadmin) {
    $query_result =
	$DB->query("SELECT pid FROM proj_memb ".
		   "WHERE uid=\"$uid\" and pid=\"$pid\"");

    if (! $query_result) {
	fatal("DB Error getting project membership for uid $uid\n");
    }
    
    if ($query_result->numrows == 0) {
	print STDOUT "Go Away! You are not a member of project $pid\n";
	exit(1);
    }
}

#
# Set the timestamp.
#
$stamp = `date '+20%y-%m-%d %H:%M:%S'`;

$query_result = $DB->query("UPDATE experiments SET expt_terminating='$stamp' ".
			   "WHERE eid='$eid' and pid='$pid'");
if (! $query_result) {
    fatal("DB Error setting expt_terminating for experiment $pid/$eid\n");
}

#
# If not in batch mode, go into the background. Parent exits.
#
if (! $batch) {
    if (background()) {
	#
	# Parent exits normally
	#
	print STDOUT
	    "Experiment $pid/$eid is now terminating\n".
	    "You will be notified via email when the experiment has been\n".
	    "torn down, and you can reuse the experiment name.\n";
	exit(0);
    }
}

print STDOUT "Running tbend with arguments: -nologfile $pid $eid\n";
if (system("$tbdir/tbend -nologfile $pid $eid") != 0) {
    fatal("tbend failed!\n");
}    

#
# Try to remove experiment directory. We allow for it not being there
# cause we often run the tb programs directly. We also allow for not
# having permission, in the case that an admin type is running this,
# in which case it won't be allowed cause of directory permissions. Thats
# okay since admin types should rarely end experiments in other projects.
#
if (chdir($expdir)) {
    print STDOUT "Removing experiment directory: $eiddir\n";
    system("rm -rf $eid");
}
else {
    print STDOUT "Not able to remove experiment directory: $eiddir\n";
    print STDOUT "Someone will need to do this by hand.\n";
}

#
# Done! Remove all trace from the DB.
# 
$query_result = $DB->query("DELETE from experiments ".
			   "WHERE eid='$eid' and pid='$pid'");

if (! $query_result) {
    fatal("DB Error deleting experiment record for $pid/$eid\n");
}

print STDOUT "Termination Success\n";

#
# In batch mode, just exit without sending email.
#
if ($batch) {
    exit(0);
}

#
# Send email notification if not in batch mode.
239
240
241
242
243
244
245
246
247
248
249
250
251
#
if (! ($MAIL = OPENMAIL("$user_name <$user_email>",
			"TESTBED: Experiment $pid/$eid Terminated",
			undef, "Bcc: $TBLOGS"))) {
    die("Cannot start mail program!");
}

print $MAIL "Experiment `$eid' in project `$pid' has been terminated.\n";
print $MAIL "You may now reuse `$eid' as an experiment name.\n\n";
print $MAIL "Appended below is the output of the experiment teardown.\n";
print $MAIL "If you have any questions or comments, please include the\n";
print $MAIL "output below in your message to $TBOPS\n";
print $MAIL "\n\n---------\n\n";
252
253
254

if (open(IN, "$logname")) {
    while (<IN>) {
255
	print $MAIL "$_";
256
257
258
    }
    close(IN);
}
259
close($MAIL);
260
261
262
263
264
265
266

unlink("$logname");
exit 0;

sub fatal()
{
    my($mesg) = $_[0];
267
268
    local $MAIL;
    
269
270
271
272
273
274
275
276
277
278
279
280
281
    print STDOUT $mesg;

    #
    # In batch mode, exit without sending the email. 
    #
    if ($batch) {
	exit(-1);
    }

    #
    # Send a message to the testbed list. Append the logfile if it got
    # that far.
    #
282
283
284
285
286
    if (! ($MAIL = OPENMAIL("$user_name <$user_email>",
			    "TESTBED: Termination Failure: $pid/$eid",
			    undef, "Cc: $TBOPS"))) {
	die("Cannot start mail program!");
    }
287

288
    print $MAIL $mesg;
289
290

    if (open(IN, "$logname")) {
291
	print $MAIL "\n\n---------\n\n";
292
293
	
	while (<IN>) {
294
	    print $MAIL "$_";
295
296
297
	}
	close(IN);
    }
298
    close($MAIL);
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
    
    unlink("$logname");
    exit(-1);
}

#
# Put ourselves into the background so that caller sees immediate response.
# Mail notification will happen later.
# 
sub background()
{
    $mypid = fork();
    if ($mypid) {
	return $mypid;
    }

    #
    # We have to disconnect from the caller by redirecting both STDIN and
    # STDOUT away from the pipe. Otherwise the caller (the web server) will
    # continue to wait even though the parent has exited. 
    #
    open(STDIN, "< /dev/null") or
	die("opening /dev/null for STDIN: $!");

    #
    # Create a temporary name for a log file and untaint it.
    #
    $logname = `mktemp /tmp/end-$pid-$eid.XXXXXX`;

    # Note different taint check (allow /).
    if ($logname =~ /^([-\@\w.\/]+)$/) {
	$logname = $1;
    } else {
	die "Bad data in $logname";
    }

    open(STDERR, ">> $logname") or die("opening $logname for STDERR: $!");
    open(STDOUT, ">> $logname") or die("opening $logname for STDOUT: $!");

    return 0;
}