. # # }}} # chdir(".."); include_once("webtask.php"); 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. # function StatusSetupAjax($needmodify) { 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) { SPITAJAX_ERROR(1, "no such instance uuid: $uuid"); 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; } # Admin users do whatever they like. if (isset($this_user) && ISADMIN()) { return 0; } # 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)))) { SPITAJAX_ERROR(1, "You do not have permission!"); return 1; } return 0; } # # Status/ # function Do_GetInstanceStatus() { global $instance, $creator; if (StatusSetupAjax(0)) { return; } $blob = array(); $blob["status"] = $instance->status(); $blob["sliverstatus"] = array(); $blob["sliverurls"] = array(); # # If we have all of our manifests, the client can request them # and show the topology. # $havemanifests = 1; foreach ($instance->slivers() as $sliver) { if (!$sliver->manifest()) { $havemanifests = 0; } else { $webtask = WebTask::Lookup($sliver->webtask_id()); if ($webtask) { $sliverstatus = $webtask->TaskValue("sliverstatus"); if ($sliverstatus) { $blob["sliverstatus"][$sliver->aggregate_urn()] = $sliverstatus; } } } if ($sliver->public_url()) { $blob["sliverurls"][$sliver->aggregate_urn()] = $sliver->public_url(); } } $blob["havemanifests"] = $havemanifests; SPITAJAX_RESPONSE($blob); } # # Terminate. # function Do_TerminateInstance() { global $instance, $creator, $this_user, $ajax_args; if (StatusSetupAjax(1)) { return; } $uuid = $instance->uuid(); $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); 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; } } # This needs work. $retval = SUEXEC("nobody", "nobody", "webmanage_instance terminate $uuid", SUEXEC_ACTION_CONTINUE); if ($retval == 0) { SPITAJAX_RESPONSE(""); return; } SPITAJAX_ERROR(-1, "Unable to Terminate; please try again later"); } # # Manifest. # function Do_GetInstanceManifest() { global $instance, $creator; if (StatusSetupAjax(0)) { return; } $blob = array(); foreach ($instance->slivers() as $sliver) { if ($sliver->manifest()) { $blob[$sliver->aggregate_urn()] = $sliver->manifest(); } } SPITAJAX_RESPONSE($blob); } # # SSH Auth Object # function Do_GetSSHAuthObject() { global $instance, $creator, $this_user; global $ajax_args; if (!isset($ajax_args["hostport"])) { SPITAJAX_ERROR(1, "Missing hostport"); return 1; } $hostport = $ajax_args["hostport"]; if (StatusSetupAjax(1)) { return; } # # XXX Need to deal with multiple members of an experiment. # if (! ((isset($this_user) && $this_user->SameUser($creator)) || (isset($_COOKIE['quickvm_user']) && $_COOKIE['quickvm_user'] == $creator->uuid()))) { SPITAJAX_ERROR(1, "Not allowed to ssh; only the creator"); return; } $nodeid = $ajax_args["nodeid"]; $auth = SSHAuthObject($creator->uid(), $hostport); if (!$auth) { SPITAJAX_ERROR(1, "Could not create authentication object"); return; } SPITAJAX_RESPONSE($auth); } # # Request automatic extension. # function Do_RequestExtension() { global $instance, $creator, $this_user, $suexec_output; global $ajax_args; global $TBMAIL_OPS, $APTMAIL, $EXTENSIONS, $APTBASE; $autoextend_maximum = TBGetSiteVar("aptui/autoextend_maximum"); $autoextend_maxage = TBGetSiteVar("aptui/autoextend_maxage"); $reason = ""; $message = ""; $needapproval = 0; $granted = 0; if (StatusSetupAjax(1)) { goto bad; } $uuid = $instance->uuid(); $slice = GeniSlice::Lookup("sa", $instance->slice_uuid()); if (!slice) { SPITAJAX_ERROR(1, "no slice for instance"); goto bad; } $expires_time = strtotime($slice->expires()); $created_time = strtotime($instance->created()); 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; } $granted = $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; } $reason = "Extended by site administrator"; } else { if (!isset($ajax_args["reason"]) || $ajax_args["reason"] == "") { SPITAJAX_ERROR(1, "Missing reason"); goto bad; } $reason = $ajax_args["reason"]; if (!TBvalid_fulltext($reason)) { SPITAJAX_ERROR(1, "Illegal characters in reason"); goto bad; } # # Guest users are treated differently. # if (!isset($this_user)) { # Only extend for 24 hours. More later. $granted = 1; if ($expires_time > time() + (3600 * 24 * $granted)) { SPITAJAX_ERROR(1, "You still have a day left. ". "Try again tomorrow"); goto delay; } } else { $diff = $expires_time - time(); $cdiff = time() - $created_time; # # 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"; } # # After maxage, all extension requests require admin approval. # elseif ($cdiff > (3600 * 24 * $autoextend_maxage)) { # # Well, unless they asked for less then the free grant, # which is a nice loophole people will probably notice. # $granted = 2; if ($wanted > $granted) { $needapproval = 1; $message = "because it was started more then ". "$autoextend_maxage days ago"; } } # # 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." # 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"; } else { if ($diff > (3600 * 24 * 7)) { SPITAJAX_ERROR(1, "You still have a week left!"); goto bad; } } # # 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. # if ($expires_time + ($granted * 3600 * 24) > time() + (3600 * 24 * $autoextend_maximum)) { $seconds = (3600 * 24 * $autoextend_maximum) - $diff; $granted = intval($seconds / (3600 * 24)); } } } if ($granted > 0) { $seconds = 3600 * 24 * $granted; $retval = SUEXEC("nobody", "nobody", "webmanage_instance extend $uuid $seconds", SUEXEC_ACTION_CONTINUE); } if ($retval == 0) { # Refresh. $slice = GeniSlice::Lookup("sa", $instance->slice_uuid()); $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())); $before = date("Y-m-d H:i:s T", strtotime($expires_time)); $now = date("Y-m-d H:i:s T", strtotime(time())); list($cluster) = Instance::ParseURN($instance->aggregate_urn()); # # 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; } SPITAJAX_RESPONSE($new_expires); $instance->SendEmail($creator->email(), "Experiment Extension: $uuid", "A request to extend your experiment was made and ". "granted.\n". "Your reason was:\n\n". $reason . "\n\n". "Your experiment was started on $created\n". "Your experiment will now expire at $new_expires\n". "It is running on $cluster\n\n\n". "$APTBASE/status.php?uuid=$uuid\n", "From: $EXTENSIONS\n" . "BCC: $EXTENSIONS"); # # 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); } $instance->BumpExtensionCount($granted); } elseif ($retval > 0) { SPITAJAX_ERROR(1, $suexec_output); goto bad; } else { SPITAJAX_ERROR(-1, "Internal Error. Please try again later"); goto bad; } return; bad: delay: sleep(1); } # # Helper for above. # function needAdminApproval($wanted, $granted, $reason, $message) { global $instance, $creator, $this_user, $suexec_output; global $EXTENSIONS, $APTBASE; $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()); $new_expires = date("Y-m-d H:i:s T", strtotime($slice->expires()) + ($howlong * 3600 * 24)); $created = date("Y-m-d H:i:s T", strtotime($instance->created())); list($cluster) = Instance::ParseURN($instance->aggregate_urn()); $instance->SendEmail($EXTENSIONS, "Experiment Extension Request: $uuid", "A request to extend this experiment was made but requires\n". "administrator approval" . ($message ? " because $message" : "") . ".\n\n" . "The request was for $wanted days, we granted $granted days, ". "the reason given is:\n\n". $reason . "\n\n". "This experiment was started on $created\n". "Granting the request would set the expiration to $new_expires\n". "It is running on $cluster\n". "\n\n". $url . "\n\n", "From: " . $creator->email()); $instance->SetExtensionReason($reason); $instance->BumpExtensionCount($granted); # XXX SPITAJAX_ERROR(2, "Your request requires admininstrator approval". ($message ? " because $message" : "") . ". " . "You will receive email if/when your ". "request is granted (or denied). Thanks!"); return; } # # Request a console URL and pass back to the status page. # function Do_ConsoleURL() { global $instance, $creator; global $ajax_args; if (!isset($ajax_args["node"])) { SPITAJAX_ERROR(1, "Missing node argument"); return 1; } $node = $ajax_args["node"]; if (StatusSetupAjax(1)) { return; } $uuid = $instance->uuid(); $slice = GeniSlice::Lookup("sa", $instance->slice_uuid()); if (!slice) { SPITAJAX_ERROR(1, "no slice for instance"); return 1; } $webtask_id = WebTask::GenerateID(); $retval = SUEXEC("nobody", "nobody", "webmanage_instance -t $webtask_id -- consoleurl $uuid " . escapeshellarg($node), SUEXEC_ACTION_CONTINUE); $webtask = WebTask::Lookup($webtask_id); if ($retval == 0) { $taskdata = $webtask->TaskData(); $blob = array(); $blob["url"] = $taskdata["url"]; if (isset($taskdata["password"])) { $blob["password"] = $taskdata["password"]; } SPITAJAX_RESPONSE($blob); $webtask->Delete(); return; } if ($webtask) { SPITAJAX_ERROR(1, $webtask->TaskValue("output")); $webtask->Delete(); } else { SPITAJAX_ERROR(-1, "Internal Error. Please try again later"); } } # # Fire off a snapshot. # function Do_Snapshot() { global $this_user; global $ajax_args; $this_idx = $this_user->uid_idx(); if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing profile uuid"); return; } $uuid = $ajax_args["uuid"]; $instance = Instance::Lookup($uuid); if (!$instance) { SPITAJAX_ERROR(1, "Unknown instance uuid"); return; } if ($this_idx != $instance->creator_idx() && !ISADMIN()) { SPITAJAX_ERROR(1, "Not enough permission. Maybe Clone instead?"); return; } 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. # $profile = Profile::Lookup($instance->profile_id(), $instance->profile_version()); 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; } $optargs = ""; if (isset($ajax_args["node_id"]) && $ajax_args["node_id"] != "") { $node_id = $ajax_args["node_id"]; if (!TBvalid_vnode_id($node_id)) { SPITAJAX_ERROR(1, "Bad node id"); return; } $optargs .= " -n $node_id "; if (isset($ajax_args["update_profile"]) && $ajax_args["update_profile"]) { $optargs .= " -u all "; } } if (isset($ajax_args["update_prepare"]) && $ajax_args["update_prepare"]) { $optargs .= " -U "; } # # Call out to the backend. # $webtask_id = WebTask::GenerateID(); $retval = SUEXEC($this_user->uid(), "nobody", "webmanage_instance -t $webtask_id -- ". " snapshot $uuid $optargs", 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")); } else { SPITAJAX_ERROR(-1, "Internal Error. Please try again later"); } if ($webtask) { $webtask->Delete(); } return; } if ($webtask && $webtask->exited()) { $webtask->Delete(); } SPITAJAX_RESPONSE("Success"); } # # Return snapshot status. # function Do_SnapshotStatus() { global $this_user; global $ajax_args; $this_idx = $this_user->uid_idx(); if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing profile uuid"); return; } $instance = Instance::Lookup($ajax_args["uuid"]); if (!$instance) { SPITAJAX_ERROR(1, "Unknown instance uuid"); return; } if ($this_idx != $instance->creator_idx() && !ISADMIN()) { SPITAJAX_ERROR(1, "Not enough permission"); return; } $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; } $blob["node_status"] = $taskdata["rawstate"]; $blob["image_status"] = $taskdata["image_status"]; if ($webtask->exited()) { # Success, but not sure what to report. Come back to this later. $blob["exited"] = $webtask->exited(); $blob["exitcode"] = $webtask->exitcode(); $webtask->Delete(); } SPITAJAX_RESPONSE($blob); } # # Ask the backend to refresh sliverstatus # function Do_Refresh() { global $this_user; global $ajax_args; $this_idx = $this_user->uid_idx(); if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing profile uuid"); return; } $uuid = $ajax_args["uuid"]; $instance = Instance::Lookup($uuid); if (!$instance) { SPITAJAX_ERROR(1, "Unknown instance uuid"); return; } if ($this_idx != $instance->creator_idx() && !ISADMIN()) { SPITAJAX_ERROR(1, "Not enough permission. Maybe Clone instead?"); return; } # # Call out to the backend. # $webtask_id = WebTask::GenerateID(); $retval = SUEXEC($this_user->uid(), "nobody", "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")); } else { SPITAJAX_ERROR(-1, "Internal Error. Please try again later"); } if ($webtask) { $webtask->Delete(); } return; } SPITAJAX_RESPONSE("Success"); } # # Ask the backend to reboot or reload a node. # function Do_RebootOrReload($which) { global $this_user; global $ajax_args; if (!isset($this_user)) { SPITAJAX_ERROR(1, "Only registered users can reboot/reload nodes"); return; } $this_idx = $this_user->uid_idx(); if (!isset($ajax_args["node_id"])) { SPITAJAX_ERROR(1, "Missing node_id"); return; } $node_id = $ajax_args["node_id"]; if (!preg_match("/^[-\w]+$/", $node_id)) { SPITAJAX_ERROR(1, "Illegal characters in node_id"); } if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing profile uuid"); return; } $uuid = $ajax_args["uuid"]; $instance = Instance::Lookup($uuid); if (!$instance) { SPITAJAX_ERROR(1, "Unknown instance uuid"); return; } if ($this_idx != $instance->creator_idx() && !ISADMIN()) { SPITAJAX_ERROR(1, "Not enough permission. Maybe Clone instead?"); return; } if ($instance->status() != "ready" && $instance->status() != "failed") { SPITAJAX_ERROR(1, "Experiment is currently busy"); return; } # # Call out to the backend. # $webtask_id = WebTask::GenerateID(); $retval = SUEXEC($this_user->uid(), "nobody", "webmanage_instance -t $webtask_id -- ". " $which $uuid " . escapeshellarg($node_id), 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")); } else { SPITAJAX_ERROR(-1, "Internal Error. Please try again later"); } if ($webtask) { $webtask->Delete(); } return; } if ($webtask) { $webtask->Delete(); } SPITAJAX_RESPONSE("Success"); } function Do_Reboot() { Do_RebootOrReload("reboot"); } function Do_Reload() { Do_RebootOrReload("reload"); } # # Set or clear the lockout flag # function Do_Lockout() { global $this_user; global $ajax_args; $this_idx = $this_user->uid_idx(); if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing profile uuid"); return; } if (!isset($ajax_args["lockout"])) { SPITAJAX_ERROR(1, "Missing lockout value"); return; } $uuid = $ajax_args["uuid"]; $instance = Instance::Lookup($uuid); if (!$instance) { SPITAJAX_ERROR(1, "Unknown instance uuid"); return; } if (!ISADMIN()) { SPITAJAX_ERROR(1, "Not enough permission."); return; } $lockout = ($ajax_args["lockout"] ? 1 : 0); if (!DBQueryWarn("update apt_instances set extension_adminonly='$lockout' ". "where uuid='$uuid'")) { SPITAJAX_ERROR(1, "Database failure."); return; } SPITAJAX_RESPONSE("Success"); } # Local Variables: # mode:php # End: ?>