status.ajax 55.2 KB
Newer Older
1
2
<?php
#
3
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 
# {{{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/>.
# 
# }}}
#
chdir("..");
25
include_once("webtask.php");
26
27
28
29
30
31
32
33
34
35
36
37
include_once("geni_defs.php");
chdir("apt");
include_once("profile_defs.php");
include_once("instance_defs.php");

# Set these globals below.
$instance = null;
$creator  = null;

#
# Locate the objects and check permission.
#
38
function StatusSetupAjax($needmodify)
39
40
41
42
43
44
45
46
47
48
49
{
    global $this_user, $ajax_args;
    global $instance, $creator;

    if (!isset($ajax_args["uuid"])) {
	SPITAJAX_ERROR(1, "Missing instance uuid");
	return 1;
    }
    $uuid = $ajax_args["uuid"];
    $instance = Instance::Lookup($uuid);
    if (!$instance) {
50
51
	SPITAJAX_ERROR(GENIRESPONSE_SEARCHFAILED,
                       "no such instance uuid: $uuid");
52
53
54
55
56
57
58
59
60
61
	return 1;
    }
    $creator = GeniUser::Lookup("sa", $instance->creator_uuid());
    if (! $creator) {
	$creator = User::LookupByUUID($instance->creator_uuid());
    }
    if (!$creator) {
	SPITAJAX_ERROR(1, "no such instance creator");
	return 1;
    }
62
    # Admin users do whatever they like.
63
64
65
    if (isset($this_user) && ISADMIN()) {
	return 0;
    }
66
67
68
69
    # Foreign admins can look.
    if (isset($this_user) && ISFOREIGN_ADMIN() && !$needmodify) {
	return 0;
    }
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    # For a guest user; must be the same guest that created experiment.
    if (get_class($creator) == "GeniUser") {
        if (isset($_COOKIE['quickvm_user']) &&
            $_COOKIE['quickvm_user'] == $creator->uuid()) {
            return 0;
        }
	SPITAJAX_ERROR(1, "You do not have permission!");
	return 1;
    }
    # An experiment created by a real user, can be accessed by other
    # members of the project, subject to modify restrictions.
    if (! (isset($this_user) && get_class($creator) == "User" &&
           $instance->CanView($this_user) &&
           (!$needmodify || $instance->CanModify($this_user)))) {
84
85
86
87
88
89
90
91
92
93
94
	SPITAJAX_ERROR(1, "You do not have permission!");
	return 1;
    }
    return 0;
}

#
# Status/
#
function Do_GetInstanceStatus()
{
95
    global $instance, $creator, $APTBASE;
96

97
    if (StatusSetupAjax(0)) {
98
99
	return;
    }
100
101
    $blob = array();
    $blob["status"] = $instance->status();
102
    $blob["canceled"] = $instance->canceled() ? 1 : 0;
103
104
    $blob["sliverstatus"] = array();
    $blob["sliverurls"]   = array();
105
106
107
108
109

    if ($instance->logfileid()) {
        $blob["logfile_url"] =
            "$APTBASE/spewlogfile.php?logfile=" . $instance->logfileid();
    }
110
    
111
112
113
114
    #
    # If we have all of our manifests, the client can request them
    # and show the topology.
    #
Leigh B Stoller's avatar
Leigh B Stoller committed
115
    $havemanifests = $instance->slivers() ? 1 : 0;
116
    # Openstack experiment?
117
    $haveopenstack = $instance->isopenstack() ? 1 : 0;
118
119
120
121
122
123

    foreach ($instance->slivers() as $sliver) {
        if (!$sliver->manifest()) {
            $havemanifests = 0;
        }
        else {
124
125
            if ($sliver->webtask_id() &&
                $webtask = WebTask::Lookup($sliver->webtask_id())) {
126
127
128
129
130
                $sliverstatus = $webtask->TaskValue("sliverstatus");
                if ($sliverstatus) {
                    $blob["sliverstatus"][$sliver->aggregate_urn()] =
                        $sliverstatus;
                }
131
132
            }
        }
133
        if ($sliver->public_url()) {
134
135
            $blob["sliverurls"][] = array("name" => $sliver->aggregate_name(),
                                          "url"  => $sliver->public_url());
136
        }
137
    }
138
    $blob["havemanifests"] = $havemanifests;
139
    $blob["haveopenstackstats"] = $haveopenstack;
140
141
142
143

    # Reflect errors back to the user.
    $webtask = WebTask::LookupByObject($instance->uuid());
    if ($webtask && $webtask->exited()) {
144
        if ($webtask->exitcode() == GENIRESPONSE_INSUFFICIENT_NODES) {
145
146
            $blob["reason"]  = "Not enough free nodes, please try again later.";
            $blob["reason"] .= "\n\n";
147
        }
148
        elseif ($webtask->exitcode() == GENIRESPONSE_INSUFFICIENT_BANDWIDTH) {
149
150
            $blob["reason"]  = "Not enough available bandwidth for a link.\n\n";
        }
151
        elseif ($webtask->exitcode() == GENIRESPONSE_NO_MAPPING) {
152
            $blob["reason"]  = "Your topology could not be mapped to physical ".
Leigh B Stoller's avatar
Leigh B Stoller committed
153
154
155
                "resources. Clicking the 'Sliver' button above will provide ".
                "lots of info, some of which might be useful in figuring out ".
                "why it failed.\n\n";
156
        }
157
        elseif ($webtask->exitcode() == GENIRESPONSE_TIMEDOUT) {
158
159
160
            $blob["reason"]  = "Your experiment timed out while setting up, ".
                "most likely because one or more nodes failed to boot. The ".
                "node consoles may provide more information.\n\n";
161
        }
162
        elseif ($webtask->exitcode() == GENIRESPONSE_BADARGS) {
163
164
165
166
167
168
            #
            # This is GENIRESPONSE_BADARGS which typically means an error
            # in the input rspec, something the user must have put in it.
            #
            $blob["reason"]  = "Your topology source contains invalid ".
                "values.\n\n";
169
        }
170
171
172
173
174
175
176
177
        elseif ($webtask->exitcode() == GENIRESPONSE_STITCHER_ERROR) {
            #
            # This is GENIRESPONSE_STITCHER_ERROR which is a catch all.
            #
            $blob["reason"]  = "Your multi-site topology could not be ".
                "stitched together. Please look closely at the ".
                "error message below to see what went wrong.\n\n";
        }
178
        else {
179
            $blob["reason"] = "";
180
        }
181
        $blob["reason"] .= htmlentities($webtask->TaskValue("output"));
182
    }
183
    SPITAJAX_RESPONSE($blob);
184
185
186
187
188
189
190
}

#
# Terminate.
#
function Do_TerminateInstance()
{
191
    global $instance, $creator, $this_user, $ajax_args;
192

193
    if (StatusSetupAjax(1)) {
194
195
196
	return;
    }
    $uuid = $instance->uuid();
197
198
199
200
201
202
203
204
205
206
    $webtask_id = WebTask::GenerateID();

    if ($instance->admin_lockdown() || $instance->user_lockdown()) {
        if (! (isset($this_user) &&
               ($instance->creator() == $this_user->uid() || ISADMIN()))) {
            SPITAJAX_ERROR(1, "Not enough permission to terminate; ".
                           "experiment is locked down");
            return;
        }
        $override = substr($uuid, 2, 5);
207

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
        if (! (isset($ajax_args["lockdown_override"]) &&
               $ajax_args["lockdown_override"] == $override)) {
            SPITAJAX_ERROR(1, "Lockdown override is incorrect");
            return;
        }
        $retval = SUEXEC($this_user->uid(), "nobody",
                         "webmanage_instance -t $webtask_id -- ".
                         "  lockdown $uuid clear all",
                         SUEXEC_ACTION_CONTINUE);
        if ($retval) {
            $webtask = WebTask::Lookup($webtask_id);

            if ($webtask && $webtask->exited()) {
                SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
                $webtask->Delete();
            }
            else {
                SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
            }
            return;
        }
    }
230
231
232
    # We do not want to see the busy errors, and that is the only error
    # that gets returned, that is not also emailed by the script. So just
    # use the ignore option.
233
    $retval = SUEXEC("nobody", "nobody",
234
                     "webmanage_instance terminate $uuid",
235
                     SUEXEC_ACTION_IGNORE);
236
237
238
239
240

    if ($retval == 0) {
	SPITAJAX_RESPONSE("");
	return;
    }
241
242
    SPITAJAX_ERROR(-1, "Your experiment is probably busy, ".
        "please try again later.");
243
244
245
246
247
248
249
250
251
}

#
# Manifest.
#
function Do_GetInstanceManifest()
{
    global $instance, $creator;

252
    if (StatusSetupAjax(0)) {
253
254
255
	return;
    }

256
257
258
259
260
261
262
263
    $blob = array();

    foreach ($instance->slivers() as $sliver) {
        if ($sliver->manifest()) {
            $blob[$sliver->aggregate_urn()] = $sliver->manifest();
        }
    }
    SPITAJAX_RESPONSE($blob);
264
265
266
267
268
269
270
}

#
# SSH Auth Object
#
function Do_GetSSHAuthObject()
{
271
    global $instance, $creator, $this_user;
272
273
274
275
276
277
278
279
    global $ajax_args;

    if (!isset($ajax_args["hostport"])) {
	SPITAJAX_ERROR(1, "Missing hostport");
	return 1;
    }
    $hostport = $ajax_args["hostport"];
    
280
    if (StatusSetupAjax(0)) {
281
282
	return;
    }
283
    if (! ((isset($this_user) && $instance->CanDoSSH($this_user)) ||
Leigh B Stoller's avatar
Leigh B Stoller committed
284
285
	   (isset($_COOKIE['quickvm_user']) &&
	    $_COOKIE['quickvm_user'] == $creator->uuid()))) {
286
287
	SPITAJAX_ERROR(1, "Not allowed to ssh; ".
                       "only the creator or a project member");
288
289
290
	return;
    }
    
291
    $nodeid = $ajax_args["nodeid"];
292
293
    $auth   = SSHAuthObject(isset($this_user) ?
                            $this_user->uid() : $creator->uid(), $hostport);
294
295
296
297
298
299
300
    if (!$auth) {
	SPITAJAX_ERROR(1, "Could not create authentication object");
	return;
    }
    SPITAJAX_RESPONSE($auth);
}

301
302
303
304
function Do_RequestExtension()
{
    global $instance, $creator, $this_user, $suexec_output;
    global $ajax_args;
305
    $inhours = 0;
306
307
308
309
310
311
312
313
314
    $autoextend_maximum = TBGetSiteVar("aptui/autoextend_maximum");

    if (StatusSetupAjax(1)) {
	goto bad;
    }
    if (isset($this_user)) {
        $project = Project::Lookup($instance->pid_idx());
        if ($project) {
            $group = $project->DefaultGroup();
315
316
            if (!(ISADMINISTRATOR() ||
                  FeatureEnabled("NewPortalExtend", $this_user, $group))) {
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
                return Do_RequestExtensionOld();
            }
        }
    }
    $uuid = $instance->uuid();
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
    if (!slice) {
	SPITAJAX_ERROR(1, "no slice for instance");
	goto bad;
    }

    if ($autoextend_maximum == 0 && !ISADMIN()) {
	SPITAJAX_ERROR(1, "Extensions are currenly disabled; please ".
                       "contact us if you need to make special arrangements.");
	goto bad;
    }

    if (!isset($ajax_args["howlong"]) || $ajax_args["howlong"] == "") {
	SPITAJAX_ERROR(1, "Missing number of days");
	goto bad;
    }
    $wanted = $ajax_args["howlong"];
    if (! preg_match("/^\d+$/", $wanted)) {
	SPITAJAX_ERROR(1, "Invalid characters in days");
	goto bad;
    }
    if (ISADMIN()) {
	if ($wanted < 1 || $wanted > 365) {
	    SPITAJAX_ERROR(1, "Must be an integer 1 <= days <= 365");
	    goto bad;
	}
	if (isset($ajax_args["reason"]) && $ajax_args["reason"] != "") {
            $reason = $ajax_args["reason"];
        }
    }
352
353
354
355
356
357
358
359
360
361
    elseif (isset($ajax_args["inhours"])) {
        #
        # For short extensions, no reason necessary.
        #
        $inhours = 1;
        if ($wanted >= 24) {
            SPITAJAX_ERROR(1, "Too big for hourly update");
            goto bad;
        }
    }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
    else {
	if (!isset($ajax_args["reason"]) || $ajax_args["reason"] == "") {
	    SPITAJAX_ERROR(1, "Missing reason");
	    goto bad;
	}
	$reason = $ajax_args["reason"];
    }
    if (isset($reason)) {
        if (!TBvalid_fulltext($reason)) {
	    SPITAJAX_ERROR(1, "Illegal characters in reason");
	    goto bad;
        }
        $filename = tempnam("/tmp", "extension");
        $fp = fopen($filename, "w");
        fwrite($fp, $reason);
        fclose($fp);
    }        
    $webtask = WebTask::CreateAnonymous();
    $retval = SUEXEC("nobody", "nobody",
		     "webmanage_instance -t " . $webtask->task_id() . " -- ".
382
383
384
                     "  extend $uuid " .
                     (isset($filename) ? "-f $filename " : "").
                     ($inhours ? "-h " : "") . "$wanted",
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
		     SUEXEC_ACTION_IGNORE);
    $webtask->Refresh();
    if (isset($filename)) {
        unlink($filename);
    }
    if ($retval != 0) {
        if ($webtask->exited()) {
            SPITAJAX_ERROR($webtask->exitcode(), $webtask->TaskValue("output"));

            #
            # This is an important error, tell tbops.
            #
            if ($webtask->exitcode() == GENIRESPONSE_REFUSED) {
                SUEXECERROR(SUEXEC_ACTION_CONTINUE);
            }
        }
        elseif ($retval < 0) {
            SPITAJAX_ERROR($retval, "Internal error, cannot proceed.");
            # Notify tbops.
            SUEXECERROR(SUEXEC_ACTION_CONTINUE);
        }
        else {
            SPITAJAX_ERROR($retval, $suexec_output);
        }
        $webtask->Delete();
        goto bad;
    }
    # Refresh. 
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
414
415
    $blob = array("expiration" => 
                  date("Y-m-d H:i:s T", strtotime($slice->expires())));
416
    $webtask->Delete();
417
    SPITAJAX_RESPONSE($blob);
418
419
420
421
422
bad:
delay:
    sleep(1);
}

423
function Do_DenyOrMoreinfo($action)
424
425
426
{
    global $instance, $creator, $this_user, $suexec_output;
    global $ajax_args;
Leigh B Stoller's avatar
Leigh B Stoller committed
427
    $extrargs = "";
428

429
430
    # Really, only admins can do this.
    if (StatusSetupAjax(0)) {
431
432
433
434
435
436
437
438
439
440
441
442
	goto bad;
    }
    $uuid = $instance->uuid();
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
    if (!slice) {
	SPITAJAX_ERROR(1, "no slice for instance");
	goto bad;
    }
    if (!ISADMIN()) {
	SPITAJAX_ERROR(1, "You do not have permission to do this.");
	goto bad;
    }
443
444
    if (isset($ajax_args["reason"]) && $ajax_args["reason"] != "") {
        $reason = $ajax_args["reason"];
445
        
446
        if (!TBvalid_fulltext($reason)) {
447
448
449
450
451
	    SPITAJAX_ERROR(1, "Illegal characters in message");
	    goto bad;
        }
        $filename = tempnam("/tmp", "extension");
        $fp = fopen($filename, "w");
452
        fwrite($fp, $reason);
453
        fclose($fp);
Leigh B Stoller's avatar
Leigh B Stoller committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    }
    if ($action == "terminate") {
        if (!isset($ajax_args["howlong"]) || $ajax_args["howlong"] == "") {
            SPITAJAX_ERROR(1, "Missing number of days");
            goto bad;
        }
        $days = $ajax_args["howlong"];
        if (! preg_match("/^\d+$/", $days)) {
            SPITAJAX_ERROR(1, "Invalid characters in days");
            goto bad;
        }
        $extrargs = $days;
    }
    $action = ($action == "deny" ? "denyextension" :
               ($action == "terminate" ? "schedterminate": "moreinfo"));
469
470
471
    $webtask = WebTask::CreateAnonymous();
    $retval = SUEXEC("nobody", "nobody",
		     "webmanage_instance -t " . $webtask->task_id() . " -- ".
Leigh B Stoller's avatar
Leigh B Stoller committed
472
                     "  $action $uuid $extrargs " .
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
                        (isset($filename) ? $filename : ""),
		     SUEXEC_ACTION_IGNORE);
    $webtask->Refresh();
    if (isset($filename)) {
        unlink($filename);
    }
    if ($retval != 0) {
        if ($webtask->exited()) {
            SPITAJAX_ERROR($webtask->exitcode(), $webtask->TaskValue("output"));

            #
            # This is an important error, tell tbops.
            #
            if ($webtask->exitcode() == GENIRESPONSE_REFUSED) {
                SUEXECERROR(SUEXEC_ACTION_CONTINUE);
            }
        }
        elseif ($retval < 0) {
            SPITAJAX_ERROR($retval, "Internal error, cannot proceed.");
            # Notify tbops.
            SUEXECERROR(SUEXEC_ACTION_CONTINUE);
        }
        else {
            SPITAJAX_ERROR($retval, $suexec_output);
        }
        $webtask->Delete();
        goto bad;
    }
    $webtask->Delete();
    SPITAJAX_RESPONSE("Success");
bad:
    $webtask->Delete();
    sleep(1);
}

508
509
510
511
512
513
514
515
function Do_DenyExtension()
{
    Do_DenyOrMoreinfo("deny");
}
function Do_MoreInfo()
{
    Do_DenyOrMoreinfo("info");
}
Leigh B Stoller's avatar
Leigh B Stoller committed
516
517
518
519
function Do_SchedTerminate()
{
    Do_DenyOrMoreinfo("terminate");
}
520

521
522
523
#
# Request automatic extension.
#
524
function Do_RequestExtensionOld()
525
{
526
    global $instance, $creator, $this_user, $suexec_output;
527
    global $ajax_args;
528
    global $TBMAIL_OPS, $APTMAIL, $EXTENSIONS, $APTBASE;
529
530
    $autoextend_maximum = TBGetSiteVar("aptui/autoextend_maximum");
    $autoextend_maxage  = TBGetSiteVar("aptui/autoextend_maxage");
531
    $reason  = "";
532
    $message = "";
533
    $needapproval = 0;
534
    $granted = 0;
535
536
537
538
539

    $uuid = $instance->uuid();
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
    if (!slice) {
	SPITAJAX_ERROR(1, "no slice for instance");
540
	goto bad;
541
542
    }
    $expires_time = strtotime($slice->expires());
543
    $created_time = strtotime($instance->created());
Leigh B Stoller's avatar
Leigh B Stoller committed
544
545
546
547
548
    $instance_name = $instance->name();
    # Until old instances are gone.
    if (!$instance_name) {
        list ($a,$b,$instance_name) = Instance::ParseURN($slice->urn());
    }
549
550
551
552
553
554
    #
    # If no physical nodes (only VMs), double the maximums.
    #
    if (!$instance->physnode_count()) {
        $autoextend_maxage  *= 2;
    }
555

556
557
558
    if ($autoextend_maximum == 0 && !ISADMIN()) {
	SPITAJAX_ERROR(1, "Extensions are currenly disabled; please ".
                       "contact us if you need to make special arrangements.");
559
560
561
	goto bad;
    }

562
563
564
565
    if (!isset($ajax_args["howlong"]) || $ajax_args["howlong"] == "") {
	SPITAJAX_ERROR(1, "Missing number of days");
	goto bad;
    }
566
567
    $granted = $wanted = $ajax_args["howlong"];
    if (! preg_match("/^\d+$/", $wanted)) {
568
569
570
571
	SPITAJAX_ERROR(1, "Invalid characters in days");
	goto bad;
    }

572
    if (ISADMIN()) {
573
	if ($wanted < 1 || $wanted > 365) {
574
	    SPITAJAX_ERROR(1, "Must be an integer 1 <= days <= 365");
575
	    goto bad;
576
	}
577
578
579
580
        $reason = "Your experiment was extended by the site administrator.\n";
	if (isset($ajax_args["reason"]) && $ajax_args["reason"] != "") {
            $reason .= "\n" . $ajax_args["reason"];
        }
581
582
583
584
    }
    else {
	if (!isset($ajax_args["reason"]) || $ajax_args["reason"] == "") {
	    SPITAJAX_ERROR(1, "Missing reason");
585
	    goto bad;
586
587
588
	}
	$reason  = $ajax_args["reason"];

589
590
591
592
593
        if (!TBvalid_fulltext($reason)) {
	    SPITAJAX_ERROR(1, "Illegal characters in reason");
	    goto bad;
        }

594
595
596
597
598
	#
	# Guest users are treated differently.
	#
	if (!isset($this_user)) {
            # Only extend for 24 hours. More later.
599
            $granted = 1;
600

601
	    if ($expires_time > time() + (3600 * 24 * $granted)) {
602
		SPITAJAX_ERROR(1, "You still have a day left. ".
603
604
			       "Try again tomorrow");
		goto delay;
605
606
607
	    }
	}
	else {
608
            $diff  = $expires_time - time();
Leigh B Stoller's avatar
Leigh B Stoller committed
609
            $cdiff = time() - $created_time;
Leigh B Stoller's avatar
Leigh B Stoller committed
610
611
612
613
614
615
616
617
618
619

            #
            # If admin lockout, we are refusing any more free time.
            #
            if ($instance->extension_lockout()) {
                $needapproval = 1;
                $granted = 0;
                $message = "because you are not allowed any more ".
                    "free extensions";
            }
620
            #
621
            # After maxage, all extension requests require admin approval.
622
            #
Leigh B Stoller's avatar
Leigh B Stoller committed
623
            elseif ($cdiff > (3600 * 24 * $autoextend_maxage)) {
624
625
626
627
                #
                # Well, unless they asked for less then the free grant,
                # which is a nice loophole people will probably notice.
                #
628
                $granted = 2;
629
630
631
632
633
634
                
                if ($wanted > $granted) {
                    $needapproval = 1;
                    $message = "because it was started more then ".
                        "$autoextend_maxage days ago";
                }
635
            }
636
637
638
            #
            # Temporary for GEC23
            #
Leigh B Stoller's avatar
Leigh B Stoller committed
639
640
            elseif (0 && (time() + ($wanted * 3600 * 24) >
                          strtotime("2015-06-15 12:00:00"))) {
641
642
643
644
                $granted = 1;
                $needapproval = 1;
                $message = "because the testbed is mostly reserved for GEC23";
            }
645
	    #
646
647
648
649
650
	    # Registered users are granted up to the autoextend_maximum
            # automatically. Beyond that, requires approval, but we still
            # give them whatever the free extension is, since we want
            # to give them extra time until the next meeting of the
            # "resource management committee."
651
	    #
652
653
654
655
656
657
658
659
660
661
662
663
664
665
	    elseif ($wanted > $autoextend_maxage) {
                #
                # We have plenty of time left, no extension just a message.
                #
                if ($diff > (3600 * 24 * 7)) {
                    needAdminApproval($wanted, 0, $reason,
                                      "because it was for longer then ".
                                      "$autoextend_maximum days");
                    goto delay;
                }
                $needapproval = 1;
                $granted = $autoextend_maximum;
                $message = "because it was for longer then ".
                    "$autoextend_maximum days";
666
	    }
667
668
669
670
671
672
            else {
                if ($diff > (3600 * 24 * 7)) {
                    SPITAJAX_ERROR(1, "You still have a week left!");
                    goto bad;
                }
            }
673
	    #
674
675
676
677
	    # The most we allow is the autoextend_maximum out, no
	    # matter what they asked for. So, if the autoextend_maximum
	    # is a week and there are five days left and they asked
	    # for seven, we give them two.
678
	    #
679
680
681
            if ($expires_time + ($granted * 3600 * 24) >
                time() + (3600 * 24 * $autoextend_maximum)) {
		$seconds = (3600 * 24 * $autoextend_maximum) - $diff;
682
                $granted = intval($seconds / (3600 * 24));
683
	    }
684
        }
685
    }
686
687
    if ($granted > 0) {
        $seconds = 3600 * 24 * $granted;
688
        $retval = SUEXEC("nobody", "nobody",
689
                         "webmanage_instance extendold $uuid $seconds",
690
                         SUEXEC_ACTION_IGNORE);
691
    }
692
693

    if ($retval == 0) {
694
	# Refresh. 
695
	$slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
696
697
	$new_expires = date("Y-m-d H:i:s T", strtotime($slice->expires()));
        $created = date("Y-m-d H:i:s T", strtotime($instance->created()));
698
699
        $before = date("Y-m-d H:i:s T", $expires_time);
        $now = date("Y-m-d H:i:s T", time());
Leigh B Stoller's avatar
Leigh B Stoller committed
700
        list($cluster) = Instance::ParseURN($instance->aggregate_urn());
701

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
        #
        # We store each extension request in an ongoing text field.
        #
        $text =
            "Date: $now\n".
            "Wanted: $wanted, Granted: $granted\n".
            "Before: $before\n".
            "After $new_expires\n".
            "Reason:\n".
            $reason . "\n\n".
            "-----------------------------------------------\n";
        $instance->AddExtensionHistory($text);

        if ($needapproval) {
            needAdminApproval($wanted, $granted, $reason, $message);
            if (!$granted)
                goto delay;
            return;
        }
721
722
	SPITAJAX_RESPONSE($new_expires);

723
	$instance->SendEmail($creator->email(),
Leigh B Stoller's avatar
Leigh B Stoller committed
724
	       "Experiment Extension: $instance_name",
725
726
727
728
729
               (ISADMIN() ? $reason :
                "A request to extend your experiment was made and ".
                "granted.\n".
                "Your reason was:\n\n". $reason) .
               "\n\n".
Leigh B Stoller's avatar
Leigh B Stoller committed
730
731
732
               "Your experiment was started on $created\n".
 	       "Your experiment will now expire at $new_expires\n".
               "It is running on $cluster\n\n\n".
733
               "$APTBASE/status.php?uuid=$uuid\n",
734
735
	       "From: $EXTENSIONS\n" .
	       "BCC: $EXTENSIONS");
736
737
738
739
740
741
742
743
        #
        # We do not want to overwrite the reason in the DB if this
        # was an admin extension, we want to keep whatever the user
        # has written previously.
        #
        if (! ISADMIN()) {
            $instance->SetExtensionReason($reason);
        }
744
        else {
745
            $instance->SetExtensionRequested(0);
746
        }
747
        $instance->BumpExtensionCount($granted);
748
749
    }
    elseif ($retval > 0) {
750
751
752
753
754
755
        #
        # This is an important error, tell tbops.
        #
        if ($retval == GENIRESPONSE_REFUSED) {
            SUEXECERROR(SUEXEC_ACTION_CONTINUE);
        }
756
	SPITAJAX_ERROR(1, $suexec_output);
757
	goto bad;
758
759
    }
    else {
760
        SUEXECERROR(SUEXEC_ACTION_CONTINUE);
761
	SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
762
	goto bad;
763
    }
764
765
    return;
bad:
766
767
768
769
770
771
772
773
774
775
delay:
    sleep(1);
}

#
# Helper for above.
#
function needAdminApproval($wanted, $granted, $reason, $message)
{
    global $instance, $creator, $this_user, $suexec_output;
776
    global $EXTENSIONS, $APTBASE;
777
778
779
780
781
782
783
784

    $uuid = $instance->uuid();
    # Subtract out the extra time we added above.
    $howlong = $wanted - $granted;
    $url = "$APTBASE/status.php?uuid=$uuid&extend=$howlong";

    # Refresh. 
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
785
    $new_expires = date("Y-m-d H:i:s T",
786
                        strtotime($slice->expires()) + ($howlong * 3600 * 24));
Leigh B Stoller's avatar
Leigh B Stoller committed
787
788
    $created = date("Y-m-d H:i:s T", strtotime($instance->created()));
    list($cluster) = Instance::ParseURN($instance->aggregate_urn());
Leigh B Stoller's avatar
Leigh B Stoller committed
789
790
791
792
793
794

    $instance_name = $instance->name();
    # Until old instances are gone.
    if (!$instance_name) {
        list ($a,$b,$instance_name) = Instance::ParseURN($slice->urn());
    }
795
		    
796
    $instance->SendEmail($EXTENSIONS,
Leigh B Stoller's avatar
Leigh B Stoller committed
797
        "Experiment Extension Request: $instance_name",
798
799
        "A request to extend this experiment was made but requires\n".
        "administrator approval" .
Leigh B Stoller's avatar
Leigh B Stoller committed
800
        ($message ? " $message" : "") . ".\n\n" .
801
802
803
        "The request was for $wanted days, we granted $granted days, ".
        "the reason given is:\n\n".
        $reason . "\n\n".
804
        "This experiment was started on $created\n".
805
        "Granting the request would set the expiration to $new_expires\n".
Leigh B Stoller's avatar
Leigh B Stoller committed
806
        "It is running on $cluster\n".
807
        "\n\n". $url . "\n\n",
808
809
810
        "From: " . $creator->email());

    $instance->SetExtensionReason($reason);
811
    $instance->BumpExtensionCount($granted);
812
    $instance->SetExtensionRequested(1);
813
814
815
816
            
    # XXX 
    SPITAJAX_ERROR(2, "Your request requires admininstrator approval".
                   ($message ? " because $message" : "") . ". " .
Leigh B Stoller's avatar
Leigh B Stoller committed
817
818
                   "You will receive email if/when your ".
                   "request is granted (or denied). Thanks!");
819
    return;
820
}
821

822
823
824
825
826
#
# Request a console URL and pass back to the status page.
#
function Do_ConsoleURL()
{
827
    global $instance, $creator, $this_user, $suexec_output;
828
829
    global $ajax_args;

830
    if (StatusSetupAjax(0)) {
831
832
833
	return;
    }
    if (!isset($this_user)) {
Leigh B Stoller's avatar
Leigh B Stoller committed
834
	SPITAJAX_ERROR(1, "Only registered users can view node consoles");
835
836
	return;
    }
837
    if (!isset($ajax_args["node"])) {
838
	SPITAJAX_ERROR(1, "Missing node argument");
839
840
841
842
843
844
845
846
847
	return 1;
    }
    $node = $ajax_args["node"];
    $uuid = $instance->uuid();
    $slice = GeniSlice::Lookup("sa", $instance->slice_uuid());
    if (!slice) {
	SPITAJAX_ERROR(1, "no slice for instance");
	return 1;
    }
848
    $webtask_id = WebTask::GenerateID();
849
    $retval = SUEXEC("nobody", "nobody",
850
851
		     "webmanage_instance -t $webtask_id -- consoleurl $uuid " .
		     escapeshellarg($node),
852
		     SUEXEC_ACTION_IGNORE);
853
    $webtask = WebTask::Lookup($webtask_id);
854
855

    if ($retval == 0) {
Leigh B Stoller's avatar
Leigh B Stoller committed
856
857
858
859
860
861
        $taskdata = $webtask->TaskData();
        $blob = array();
        $blob["url"] = $taskdata["url"];
        if (isset($taskdata["password"])) {
            $blob["password"] = $taskdata["password"];
        }
862
863
864
        if (isset($taskdata["logurl"])) {
            $blob["logurl"] = $taskdata["logurl"];
        }
Leigh B Stoller's avatar
Leigh B Stoller committed
865
	SPITAJAX_RESPONSE($blob);
866
867
	$webtask->Delete();
	return;
868
    }
869
870
871
    elseif ($retval < 0) {
        SUEXECERROR(SUEXEC_ACTION_CONTINUE);
    }
872
    if ($webtask) {
Leigh B Stoller's avatar
Leigh B Stoller committed
873
	SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
874
	$webtask->Delete();
875
    }
876
877
878
    elseif ($suexec_output != "") {
        SPITAJAX_ERROR(1, $suexec_output);
    }
879
880
881
882
883
    else {
	SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
    }
}

884
885
886
887
888
#
# Fire off a snapshot.
#
function Do_Snapshot()
{
889
    global $this_user, $instance, $suexec_output;
890
891
    global $ajax_args;

892
    if (StatusSetupAjax(0)) {
893
894
	return;
    }
895
896
    if (!isset($this_user)) {
	SPITAJAX_ERROR(1, "Only registered users can snapshot nodes");
897
898
	return;
    }
899
900
901
    $this_idx = $this_user->uid_idx();
    $uuid = $ajax_args["uuid"];

902
903
904
905
906
907
908
909
910
911
912
913
    #
    # As per Rob, if an experiment is locked down, then only the creator,
    # project leader, or an admininstrator.
    #
    if ($instance->admin_lockdown() || $instance->user_lockdown()) {
        if ($this_idx != $instance->creator_idx() && !ISADMIN() &&
            !$instance->Project()->IsLeader($this_user)) {
            SPITAJAX_ERROR(1, "Not enough permission, ".
                           "experiment is locked down. Maybe Clone instead?");
            return;
        }
    }
914
915
916
917
918
919
920
921
    if ($instance->status() != "ready") {
	SPITAJAX_ERROR(1, "Experiment is currently busy");
	return;
    }
    #
    # The profile also has to belong to the user, since it is
    # going to be modified to use the new image.
    #
922
923
    $profile = Profile::Lookup($instance->profile_id(),
			       $instance->profile_version());
924
925
926
927
928
929
930
931
    if (!$profile) {
	SPITAJAX_ERROR(1, "Cannot lookup profile for instance");
	return;
    }
    if ($this_idx != $profile->creator_idx() && !ISADMIN()) {
	SPITAJAX_ERROR(1, "Not your profile to change. Clone first!");
	return;
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
932
    $optargs = "";
Leigh B Stoller's avatar
Leigh B Stoller committed
933
    if (isset($ajax_args["node_id"]) && $ajax_args["node_id"] != "") {
Leigh B Stoller's avatar
Leigh B Stoller committed
934
935
936
937
938
        $node_id = $ajax_args["node_id"];
        if (!TBvalid_vnode_id($node_id)) {
            SPITAJAX_ERROR(1, "Bad node id");
            return;
        }
939
        $optargs .= " -n $node_id ";
Leigh B Stoller's avatar
Leigh B Stoller committed
940
941
942
943
944
945

        if (isset($ajax_args["update_profile"]) &&
            $ajax_args["update_profile"]) {
            $optargs .= " -u all ";
        }
    }
946
947
948
949
950
951
    else {
        #
        # Single node profile snapshot, we always update the profile.
        #
        $optargs .= " -u node ";
    }
952
953
954
955
    if (isset($ajax_args["update_prepare"]) &&
        $ajax_args["update_prepare"]) {
        $optargs .= " -U ";
    }
956
957
958
959
960
961
962
    if (isset($ajax_args["imagename"]) && $ajax_args["imagename"] != "") {
        if (!TBvalid_imagename($ajax_args["imagename"])) {
            SPITAJAX_ERROR(1, "Not a valid imagename, alphanumeric only");
            return;
        }
        $optargs .= " -i " . escapeshellarg($ajax_args["imagename"]);
    }
963
964
965
966
    
    #
    # Call out to the backend.
    #
967
    $webtask_id = WebTask::GenerateID();
968
    $retval = SUEXEC($this_user->uid(), "nobody",
Leigh B Stoller's avatar
Leigh B Stoller committed
969
		     "webmanage_instance -t $webtask_id -- ".
970
                     "  snapshot $uuid $optargs",
971
		     SUEXEC_ACTION_IGNORE);
972
    $webtask = WebTask::Lookup($webtask_id);
973
974
975

    if ($retval != 0) {
	if ($retval < 0) {
976
	    SPITAJAX_ERROR(-11, "Internal error, cannot proceed.");
977
978
979
	    # Notify tbops.
	    SUEXECERROR(SUEXEC_ACTION_CONTINUE);
	}
980
	elseif ($webtask) {
Leigh B Stoller's avatar
Leigh B Stoller committed
981
	    SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
982
	}
983
984
985
	elseif ($suexec_output != "") {
	    SPITAJAX_ERROR(1, $suexec_output);
        }
986
	else {
987
	    SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
988
	}
989
990
991
	if ($webtask) {
	    $webtask->Delete();
	}
992
	return;
993
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
994
    if ($webtask && $webtask->exited()) {
995
996
	$webtask->Delete();
    }
997
998
999
1000
1001
1002
1003
1004
    SPITAJAX_RESPONSE("Success");
}

#
# Return snapshot status.
#
function Do_SnapshotStatus()
{
1005
    global $this_user, $instance;
1006
1007
    global $ajax_args;

1008
    if (StatusSetupAjax(0)) {
1009
1010
	return;
    }
1011
1012
    if (!isset($this_user)) {
	SPITAJAX_ERROR(1, "Only registered users can snapshot nodes");
1013
1014
	return;
    }
1015
    $this_idx = $this_user->uid_idx();
1016
    
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
    $webtask = WebTask::LookupByObject($instance->uuid());
    if (!$webtask) {
	SPITAJAX_ERROR(1, "No status descriptor found");
	return;
    }
    $taskdata = $webtask->TaskData();
    $blob = array();

    #
    # Size is in KB to avoid bigint problems. But kill the KB.
    # 
    if (isset($taskdata["image_size"])) {
	if (preg_match("/^(\d+)KB$/", $taskdata["image_size"], $matches)) {
	    $taskdata["image_size"] = $matches[1]; 
	}
	$blob["image_size"] = $taskdata["image_size"];
    }
    else {
	$blob["image_size"] = 0;
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
1037
1038
1039
    if (isset($taskdata["copyback_uuid"])) {
        $blob["copyback_uuid"] = $taskdata["copyback_uuid"];
    }
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
    $blob["image_status"] = $taskdata["image_status"];
    
    #
    # Lets put the node status in too. The backend has helpfully told us
    # the aggregate and node to track down the status.
    #
    if (isset($taskdata["aggregate_urn"]) && isset($taskdata["client_id"])) {
        $sliver = InstanceSliver::Lookup($instance, $taskdata["aggregate_urn"]);
        if ($sliver) {
            $slwebtask = WebTask::Lookup($sliver->webtask_id());
            $sliverstatus = $slwebtask->TaskValue("sliverstatus");
            if ($sliverstatus) {
                foreach ($sliverstatus as $node_id => $node_status) {
                    if ($node_id == $taskdata["client_id"]) {
                        $blob["node_status"] = $node_status["rawstate"];
                        break;
                    }
                }
            }
        }
    }
1061
1062
1063
1064
    if ($webtask->exited()) {
	# Success, but not sure what to report. Come back to this later.
	$blob["exited"]   = $webtask->exited();
	$blob["exitcode"] = $webtask->exitcode();
1065
1066
1067
        if (isset($taskdata["image_name"])) {
            $blob["image_name"] = $taskdata["image_name"];
        }
1068
1069
	$webtask->Delete();
    }
1070
1071
1072
    SPITAJAX_RESPONSE($blob);
}

1073
1074
1075
1076
1077
#
# Ask the backend to refresh sliverstatus
#
function Do_Refresh()
{
1078
    global $this_user, $instance, $suexec_output;
1079
1080
    global $ajax_args;

1081
1082
1083
    #
    # Guest users can do this.
    #
1084
    if (StatusSetupAjax(0)) {
1085
1086
	return;
    }
1087
    $this_idx = $this_user->uid_idx();
1088
1089
1090
1091
1092
    $uuid = $ajax_args["uuid"];
    #
    # Call out to the backend.
    #
    $webtask_id = WebTask::GenerateID();
1093
    $retval = SUEXEC("nobody", "nobody",
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
		     "webmanage_instance -t $webtask_id -- refresh $uuid",
		     SUEXEC_ACTION_IGNORE);
    $webtask = WebTask::Lookup($webtask_id);

    if ($retval != 0) {
	if ($retval < 0) {
	    SPITAJAX_ERROR(-11, "Internal error, cannot proceed.");
	    # Notify tbops.
	    SUEXECERROR(SUEXEC_ACTION_CONTINUE);
	}
	elseif ($webtask) {
	    SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
	}
1107
1108
1109
	elseif ($suexec_output != "") {
	    SPITAJAX_ERROR(1, $suexec_output);
        }
1110
1111
1112
1113
1114
1115
1116
1117
	else {
	    SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
	}
	if ($webtask) {
	    $webtask->Delete();
	}
	return;
    }
1118
    SPITAJAX_RESPONSE("Success");
1119
1120
}

Leigh B Stoller's avatar
Leigh B Stoller committed
1121
1122
1123
1124
1125
1126
1127
1128
#
# Reload rhe manifests.
#
function Do_ReloadTopology()
{
    global $this_user, $instance, $suexec_output;
    global $ajax_args;

1129
    if (StatusSetupAjax(0)) {
Leigh B Stoller's avatar
Leigh B Stoller committed
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
	return;
    }
    $this_idx = $this_user->uid_idx();
    $uuid = $ajax_args["uuid"];
    #
    # Call out to the backend.
    #
    $webtask_id = WebTask::GenerateID();
    $retval = SUEXEC("nobody", "nobody",
		     "webmanage_instance -t $webtask_id -- getmanifests $uuid",
		     SUEXEC_ACTION_IGNORE);
    $webtask = WebTask::Lookup($webtask_id);

    if ($retval != 0) {
	if ($retval < 0) {
	    SPITAJAX_ERROR(-11, "Internal error, cannot proceed.");
	    # Notify tbops.
	    SUEXECERROR(SUEXEC_ACTION_CONTINUE);
	}
	elseif ($webtask) {
	    SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
	}
	elseif ($suexec_output != "") {
	    SPITAJAX_ERROR(1, $suexec_output);
        }
	else {
	    SPITAJAX_ERROR(-1, "Internal Error. Please try again later");
	}
	if ($webtask) {
	    $webtask->Delete();
	}
	return;
    }
    SPITAJAX_RESPONSE("Success");
}

1166
#
1167
# Ask the backend to reboot or reload a node.
1168
#
1169
function Do_RebootOrReload($which)
1170
{
1171
    global $this_user, $instance, $suexec_output;
1172
1173
    global $ajax_args;

1174
    if (StatusSetupAjax(0)) {
1175
1176
	return;
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
1177
1178
1179
1180
    if (!isset($this_user)) {
	SPITAJAX_ERROR(1, "Only registered users can reboot/reload nodes");
	return;
    }
1181
1182
1183
    $this_idx = $this_user->uid_idx();
    $uuid = $ajax_args["uuid"];

1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
    #
    # As per Rob, if an experiment is locked down, then only the creator,
    # project leader, or an admininstrator.
    #
    if ($instance->admin_lockdown() || $instance->user_lockdown()) {
        if ($this_idx != $instance->creator_idx() && !ISADMIN() &&
            !$instance->Project()->IsLeader($this_user)) {
            SPITAJAX_ERROR(1, "Not enough permission, ".
                           "experiment is locked down");
            return;
        }
    }
1196

Leigh B Stoller's avatar
Leigh B Stoller committed
1197
1198
    if (!isset($ajax_args["node_ids"])) {
	SPITAJAX_ERROR(1, "Missing node_id list");
1199
1200
	return;
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
1201
1202
1203
1204
1205
1206
1207
    $nodelist = "";
    foreach ($ajax_args["node_ids"] as $node_id) {
        if (!preg_match("/^[-\w]+$/", $node_id)) {
            SPITAJAX_ERROR(1, "Illegal characters in node_id");
            return;
        }
        $nodelist .= " " . escapeshellarg($node_id);
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
    }
    if ($instance->status() != "ready" && $instance->status() != "failed") {
	SPITAJAX_ERROR(1, "Experiment is currently busy");
	return;
    }

    #
    # Call out to the backend.
    #
    $webtask_id = WebTask::GenerateID();
1218
    $retval = SUEXEC("nobody", "nobody",
1219
		     "webmanage_instance -t $webtask_id -- ".
Leigh B Stoller's avatar
Leigh B Stoller committed
1220
                     "  $which $uuid $nodelist",
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
		     SUEXEC_ACTION_IGNORE);
    $webtask = WebTask::Lookup($webtask_id);

    if ($retval != 0) {
	if ($retval < 0) {
	    SPITAJAX_ERROR(-11, "Internal error, cannot proceed.");
	    # Notify tbops.
	    SUEXECERROR(SUEXEC_ACTION_CONTINUE);
	}
	elseif ($webtask) {
	    SPITAJAX_ERROR(1, $webtask->TaskValue("output"));
	}
1233
1234
1235
1236
	elseif ($suexec_output != "") {
	    SPITAJAX_ERROR(1, $suexec_output);
        }
        else {
Leigh B Stoller's avatar
Leigh B Stoller committed