protected function purgeData($timestamp, $batchSize) { $date = swarmdb_dateformat($timestamp); // Based on ManageProjectScript::delete() $stats = array(); $db = $this->getContext()->getDB(); while (true) { $jobRows = $db->getRows(str_queryf('SELECT id FROM jobs WHERE created < %s LIMIT %u;', $date, $batchSize)); if (!$jobRows) { // Done break; } $jobIDs = array_map(function ($row) { return $row->id; }, $jobRows); $this->out('...deleting ' . count($jobIDs) . ' jobs'); $action = WipejobAction::newFromContext($this->getContext()); $result = $action->doWipeJobs('delete', $jobIDs, $batchSize); $this->mergeStats($stats, $result); } // TODO: Purge rows from clients table for clients that are no // longer active and don't have 0 runresults after the purge. foreach ($stats as $key => $val) { $this->out("deleted {$key} rows: {$val}"); } $this->out(''); $this->out('Done!'); }
/** * @actionNote This action takes no parameters. */ public function doAction() { $context = $this->getContext(); $browserInfo = $context->getBrowserInfo(); $db = $context->getDB(); $conf = $context->getConf(); $request = $context->getRequest(); $resetTimedoutRuns = 0; // Get clients that are considered disconnected (not responding to the latest pings). // Then mark the runresults of its active runs as timed-out, and reset those runs so // they become available again for different clients in GetrunAction. $rows = $db->getRows(str_queryf("SELECT\n\t\t\t\trunresults.id as id\n\t\t\tFROM\n\t\t\t\trunresults\n\t\t\tINNER JOIN clients ON runresults.client_id = clients.id\n\t\t\tWHERE runresults.status = 1\n\t\t\tAND clients.updated < %s;", swarmdb_dateformat(Client::getMaxAge($context)))); if ($rows) { foreach ($rows as $row) { // Reset the run $ret = $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\tstatus = 0,\n\t\t\t\t\t\tresults_id = NULL\n\t\t\t\t\tWHERE results_id = %u;", $row->id)); // If the previous UPDATE query failed for whatever // reason, don't do the below query as that will lead // to data corruption (results with state LOST must never // be referenced from run_useragent.results_id). if ($ret) { // Update status of the result $ret = $db->query(str_queryf("UPDATE runresults\n\t\t\t\t\t\tSET status = %s\n\t\t\t\t\t\tWHERE id = %u;", ResultAction::$STATE_LOST, $row->id)); } if ($ret) { $resetTimedoutRuns++; } } } $this->setData(array("resetTimedoutRuns" => $resetTimedoutRuns)); }
/** * @actionNote This action takes no parameters. */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $db = $this->getContext()->getDB(); $conf = $this->getContext()->getConf(); $request = $this->getContext()->getRequest(); // Get runs that were given to a client (status=1), // but haven't pinged back when they should. $maxage = time() - $conf->client->runTimeout - $conf->client->saveRetryMax * ($conf->client->saveReqTimeout + $conf->client->saveRetrySleep); $rows = $db->getRows(str_queryf("SELECT\n\t\t\t\tid,\n\t\t\t\tresults_id\n\t\t\tFROM\n\t\t\t\trun_useragent\n\t\t\tWHERE status = 1\n\t\t\tAND updated < %s;", swarmdb_dateformat($maxage))); $resetTimedoutRuns = 0; // For clients that have stopped pinging, // assume disconnection (browser crashed, network lost, closed browser, ..) // @todo: Incorrect, the above query finds runs that have timed out. // Not dead runs from no longer connected clients, both should be checked. // The latter involves 3 cross-checks. Get runresults entry. Get client_id. // Get clients entry. Check updated property against pingTime+pingTimeMargin (see UserAction/SwarmstateAction). // Make 2 arrays of runUaIds and runresultsIds and unique them before the if(). Change if to if-count() if ($rows) { $resetTimedoutRuns = count($rows); foreach ($rows as $row) { $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\tstatus = 0,\n\t\t\t\t\t\tresults_id = NULL\n\t\t\t\t\tWHERE id = %u;", $row->id)); // Record runresults status as having timed-out (status=3) $db->query(str_queryf("UPDATE runresults\n\t\t\t\t\tSET status = %s\n\t\t\t\t\tWHERE id = %u;", ResultAction::$STATE_LOST, $row->results_id)); } } $this->setData(array("resetTimedoutRuns" => $resetTimedoutRuns)); }
/** * @actionNote This action takes no parameters. */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); // Get runs that were given to a client (status=1), // but haven't responded with a save (status=2) within 5 minutes. $rows = $db->getRows(str_queryf("SELECT\n\t\t\t\trun_id,\n\t\t\t\tclient_id,\n\t\t\t\tuseragent_id\n\t\t\tFROM\n\t\t\t\trun_client, clients\n\t\t\tWHERE run_client.updated < %s\n\t\t\tAND clients.id = run_client.client_id\n\t\t\tAND run_client.status = 1;", swarmdb_dateformat(strtotime('5 minutes ago')))); $resetTimedoutRuns = 0; if ($rows) { $resetTimedoutRuns = count($rows); foreach ($rows as $row) { // Undo runcount and reset status $db->query(str_queryf("UPDATE\n\t\t\t\t\t\trun_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\truns = runs - 1,\n\t\t\t\t\t\tstatus = 0\n\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\tAND useragent_id = %s;", $row->run_id, $row->useragent_id)); // Remove run_client entry, // after 5 minutes we'll assume the client crashed, refreshed, closed the browser // or something else... $db->query(str_queryf("DELETE FROM\n\t\t\t\t\t\trun_client\n\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\tAND client_id = %u;", $row->run_id, $row->client_id)); } } // Reset runs that race-condition deleted themselves $db->query("UPDATE\n\t\t\t\trun_useragent\n\t\t\tSET\n\t\t\t\truns = 0,\n\t\t\t\tcompleted = 0,\n\t\t\t\tstatus = 0\n\t\t\tWHERE runs = max\n\t\t\tAND NOT EXISTS (\n\t\t\t\tSELECT *\n\t\t\t\tFROM run_client, clients\n\t\t\t\tWHERE run_client.run_id = run_useragent.run_id\n\t\t\t\tAND run_client.client_id = clients.id\n\t\t\t\tAND clients.useragent_id = run_useragent.useragent_id\n\t\t\t);"); $resetRaceConditionDeleted = $db->getAffectedRows(); $this->setData(array("resetTimedoutRuns" => $resetTimedoutRuns, "resetRaceConditionDeleted" => $resetRaceConditionDeleted)); }
protected function loadNew() { $browserInfo = $this->context->getBrowserInfo(); $db = $this->context->getDB(); $request = $this->context->getRequest(); // If the useragent isn't known, abort with an error message if (!$browserInfo->isInSwarmUaIndex()) { throw new SwarmException('Your browser is not needed by this swarm.'); } $clientName = $request->getVal('item', 'anonymous'); if (!$clientName) { // The UI javascript injects a default value and if the field is missing // the above WebRequest#getVal fallback catches it. But if the field // was submitted with an empty string, then just ignore it and go to anonymous as well. // We don't want to hold back potential swarm joiners. $clientName = 'anonymous'; } if (!self::isValidName($clientName)) { throw new SwarmException('Invalid client name. Names should be no longer than 128 characters.'); } // Insert in a new record for the client and get its ID $db->query(str_queryf('INSERT INTO clients (name, useragent_id, useragent, ip, updated, created) VALUES(%s, %s, %s, %s, %s, %s);', $clientName, $browserInfo->getSwarmUaID(), $browserInfo->getRawUA(), $request->getIP(), swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $this->clientRow = $db->getRow(str_queryf('SELECT * FROM clients WHERE id = %u LIMIT 1;', $db->getInsertId())); }
/** * @actionMethod POST: Required. * @actionParam int job_id * @actionParam string type: one of 'delete', 'reset'. * @actionAuth: Required. */ public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $jobID = $request->getInt('job_id'); $wipeType = $request->getVal('type'); if (!$jobID || !$wipeType) { $this->setError('missing-parameters'); return; } if (!in_array($wipeType, array('delete', 'reset'))) { $this->setError('invalid-input', 'Invalid wipeType'); return; } $projectID = $db->getOne(str_queryf('SELECT project_id FROM jobs WHERE id = %u;', $jobID)); if (!$projectID) { $this->setError('invalid-input', 'Job not found'); return; } // Check authentication if (!$this->doRequireAuth($projectID)) { return; } $runRows = $db->getRows(str_queryf('SELECT id FROM runs WHERE job_id = %u;', $jobID)); if ($runRows) { foreach ($runRows as $runRow) { if ($wipeType === 'delete') { $db->query(str_queryf('DELETE FROM run_useragent WHERE run_id = %u;', $runRow->id)); } elseif ($wipeType === 'reset') { $db->query(str_queryf('UPDATE run_useragent SET status = 0, completed = 0, results_id = NULL, updated = %s WHERE run_id = %u;', swarmdb_dateformat(SWARM_NOW), $runRow->id)); } } } // This should be outside the if for $runRows, because jobs // can sometimes be created without any runs (by accidently). // Those should be deletable as well, thus this has to be outside the loop. // Also, no need to do this in a loop, just delete them all in one query. if ($wipeType === 'delete') { $db->query(str_queryf('DELETE FROM runs WHERE job_id = %u;', $jobID)); $db->query(str_queryf('DELETE FROM jobs WHERE id = %u;', $jobID)); } $this->setData(array('jobID' => $jobID, 'type' => $wipeType, 'result' => 'ok')); }
protected function loadNew() { $browserInfo = $this->context->getBrowserInfo(); $db = $this->context->getDB(); $request = $this->context->getRequest(); // If the useragent isn't known, abort with an error message if (!$browserInfo->isInSwarmUaIndex()) { throw new SwarmException("Your browser is not suported in this TestSwarm " . "(useragent string: {$browserInfo->getRawUA()})."); } // Running a client doesn't require being logged in $username = $request->getSessionData("username", $request->getVal("item")); if (!$username) { throw new SwarmException("Username required."); } // Figure out what the user's ID number is $userRow = $db->getRow(str_queryf("SELECT * FROM users WHERE name = %s LIMIT 1;", $username)); // If the user doesn't have one, create a new user row for this name if (!$userRow || !$userRow->id) { $db->query(str_queryf("INSERT INTO users (name, updated, created) VALUES(%s, %s, %s);", $username, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $userRow = $db->getRow(str_queryf("SELECT * FROM users WHERE id = %u LIMIT 1;", $db->getInsertId())); } // Insert in a new record for the client and get its ID $db->query(str_queryf("INSERT INTO clients (user_id, useragent_id, useragent, ip, updated, created)\n\t\t\tVALUES(%u, %s, %s, %s, %s, %s);", $userRow->id, $browserInfo->getSwarmUaID(), $browserInfo->getRawUA(), $request->getIP(), swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $this->clientRow = $db->getRow(str_queryf("SELECT * FROM clients WHERE id = %u LIMIT 1;", $db->getInsertId())); $this->userRow = $userRow; }
/** * @requestParam browserSet string: Show useragents from a specific * browserset only. * @requestParam onlyactive bool: If true, only user agents that * have online clients and/or pending runs are included. * If both "browserSet" and "onlyactive" are used, the overlaping * subset will be output. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $showOnlyactive = $request->hasKey("onlyactive"); $filterBrowserSet = $request->getVal("browserSet", false); $data = array("userAgents" => array()); $uaIndex = BrowserInfo::getSwarmUAIndex(); foreach ($uaIndex as $uaID => $uaData) { if ($filterBrowserSet && isset($conf->browserSets->{$filterBrowserSet}) && !in_array($uaID, $conf->browserSets->{$filterBrowserSet})) { continue; } // Count online clients with this UA $clients = $db->getOne(str_queryf("SELECT\n\t\t\t\t\tCOUNT(id)\n\t\t\t\tFROM clients\n\t\t\t\tWHERE useragent_id = %s\n\t\t\t\tAND updated > %s", $uaID, swarmdb_dateformat(strtotime('1 minute ago')))); $clients = intval($clients); // Count pending runs for this UA $pendingRuns = $db->getOne(str_queryf("SELECT\n\t\t\t\t\tCOUNT(*)\n\t\t\t\tFROM run_useragent\n\t\t\t\tWHERE useragent_id = %s\n\t\t\t\tAND status = 0;", $uaID)); $pendingRuns = intval($pendingRuns); // Count past runs that can still be re-run to // possibly fix non-passing results $pendingReRuns = $db->getOne(str_queryf("SELECT\n\t\t\t\t\tCOUNT(*)\n\t\t\t\tFROM run_useragent\n\t\t\t\tWHERE useragent_id = %s\n\t\t\t\tAND runs < max\n\t\t\t\tAND completed > 0;", $uaID)); $pendingReRuns = intval($pendingReRuns); if ($showOnlyactive && !$clients && !$pendingRuns && !$pendingReRuns) { continue; } $data["userAgents"][$uaID] = array("data" => $uaData, "stats" => array("onlineClients" => $clients, "pendingRuns" => $pendingRuns, "pendingReRuns" => $pendingReRuns)); } $this->setData($data); }
/** * @actionMethod POST: Required. * @actionParam client_id int * @actionParam run_token string * @actionParam run_id int * @actionParam fail int * @actionParam error int * @actionParam total int * @actionParam results string: HTML snapshot of the test results page. */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $runToken = $request->getVal("run_token"); if ($conf->client->requireRunToken && !$runToken) { $this->setError("invalid-input", "This TestSwarm does not allow unauthorized clients to join the swarm."); return; } $clientID = $request->getInt("client_id"); if (!$clientID) { $this->setError("invalid-input"); return; } // Create a Client object that verifies client id, user agent and run token. // Also updates the client 'alive' timestamp. // Throws exception (caught higher up) if stuff is invalid. $client = Client::newFromContext($this->getContext(), $runToken, $clientID); $runID = $request->getInt("run_id"); $fail = $request->getInt("fail"); $error = $request->getInt("error"); $total = $request->getInt("total"); $results = gzencode($request->getVal("results", "")); $db->query(str_queryf("UPDATE\n\t\t\t\trun_client\n\t\t\tSET\n\t\t\t\tstatus = 2,\n\t\t\t\tfail = %u,\n\t\t\t\terror = %u,\n\t\t\t\ttotal = %u,\n\t\t\t\tresults = %s,\n\t\t\t\tupdated = %s\n\t\t\tWHERE client_id = %u\n\t\t\tAND run_id = %u\n\t\t\tLIMIT 1;", $fail, $error, $total, $results, swarmdb_dateformat(SWARM_NOW), $clientID, $runID)); if (mysql_affected_rows() > 0) { // If we're 100% passing we don't need any more runs // Clear out other entries from other browsers for the same run // that were bad, since we now have a good one. if ($total > 0 && $fail === 0 && $error === 0) { $rows = $db->getRows(str_queryf("SELECT client_id\n\t\t\t\t\tFROM\n\t\t\t\t\t\trun_client, clients\n\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\tAND client_id != %u\n\t\t\t\t\tAND (total <= 0 OR error > 0 OR fail > 0)\n\t\t\t\t\tAND clients.id = client_id\n\t\t\t\t\tAND clients.useragent_id = %s;", $runID, $clientID, $client->getClientRow()->useragent_id)); if ($rows) { foreach ($rows as $row) { $db->query(str_queryf("DELETE\n\t\t\t\t\t\t\tFROM run_client\n\t\t\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\t\t\tAND client_id = %u;", $runID, $row->client_id)); } } $db->query(str_queryf("UPDATE\n\t\t\t\t\t\trun_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\truns = max,\n\t\t\t\t\t\tcompleted = completed + 1,\n\t\t\t\t\t\tstatus = 2,\n\t\t\t\t\t\tupdated = %s\n\t\t\t\t\tWHERE useragent_id = %s\n\t\t\t\t\tAND run_id = %u\n\t\t\t\t\tLIMIT 1;", swarmdb_dateformat(SWARM_NOW), $browserInfo->getSwarmUaID(), $runID)); } else { // Clear out old runs that timed out. if ($total > 0) { $rows = $db->getRows(str_queryf("SELECT\n\t\t\t\t\t\t\tclient_id\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\trun_client\n\t\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\t\tAND client_id != %u\n\t\t\t\t\t\tAND total <= 0;", $runID, $clientID)); if ($rows) { foreach ($rows as $row) { $db->query(str_queryf("DELETE\n\t\t\t\t\t\t\t\tFROM run_client\n\t\t\t\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\t\t\t\tAND client_id = %u;", $runID, $row->client_id)); } } } $db->query(str_queryf("UPDATE\n\t\t\t\t\t\trun_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\tcompleted = completed + 1,\n\t\t\t\t\t\tstatus = IF(completed + 1 < max, 1, 2),\n\t\t\t\t\t\tupdated = %s\n\t\t\t\t\tWHERE useragent_id = %s\n\t\t\t\t\tAND run_id = %u\n\t\t\t\t\tLIMIT 1;", swarmdb_dateformat(SWARM_NOW), $browserInfo->getSwarmUaID(), $runID)); } } $this->setData("ok"); }
public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $runID = $request->getInt("run_id"); $clientID = $request->getInt("client_id"); $useragentID = $request->getVal("useragent_id"); if (!$runID || !$clientID) { $this->setError("missing-parameters"); return; } $jobID = (int) $db->getOne(str_queryf('SELECT job_id FROM runs WHERE id = %u;', $runID)); if (!$jobID) { $this->setError("invalid-input", "Run {$runID} not found."); return; } $jobOwner = $db->getOne(str_queryf('SELECT users.name as user_name FROM jobs, users WHERE jobs.id = %u AND users.id = jobs.user_id LIMIT 1;', $jobID)); if (!$jobOwner) { $this->setError("invalid-input", "Job {$jobID} not found."); return; } // Check authentication $userId = $this->doRequireAuth($jobOwner); if (!$userId) { return; } $runJobID = (int) $db->getOne(str_queryf('SELECT job_id FROM runs WHERE id = %u;', $runID)); if ($runJobID !== $jobID) { $this->setError("invalid-input", "Run {$runID} does not belong to job {$jobID}."); return; } $clientUseragentID = $db->getOne(str_queryf('SELECT useragent_id FROM clients WHERE id = %u;', $clientID)); if ($clientUseragentID !== $useragentID) { $this->setError("invalid-input", "Client {$clientID} does not run useragent {$useragentID}"); return; } $db->query(str_queryf('UPDATE run_useragent SET status = 0, completed = 0, results_id = NULL, updated = %s WHERE run_id = %u AND useragent_id = %s;', swarmdb_dateformat(SWARM_NOW), $runID, $useragentID)); $this->setData(array("jobID" => $jobID, "runID" => $runID, "clientID" => $clientID, "useragentID" => $useragentID, "result" => "ok")); }
/** * @actionMethod POST: Required. * @actionParam int job_id * @actionParam string type: one of 'delete', 'reset' */ public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $jobID = $request->getInt("job_id"); $wipeType = $request->getVal("type"); if (!$jobID || !$wipeType) { $this->setError("missing-parameters"); return; } if (!in_array($wipeType, array("delete", "reset", "cancel"))) { $this->setError("invalid-input"); return; } $jobOwner = $db->getOne(str_queryf("SELECT\n\t\t\t\tusers.name as user_name\n\t\t\tFROM jobs, users\n\t\t\tWHERE jobs.id = %u\n\t\t\tAND users.id = jobs.user_id\n\t\t\tLIMIT 1;", $jobID)); if (!$jobOwner) { // Job row by this ID didn't exist $this->setError("invalid-input"); return; } // Check authentication if ($request->getSessionData("auth") !== "yes" || $request->getSessionData("username") !== $jobOwner) { $this->setError("requires-auth"); return; } $runRows = $db->getRows(str_queryf("SELECT id\n\t\t\tFROM runs\n\t\t\tWHERE job_id = %u;", $jobID)); if ($runRows) { foreach ($runRows as $runRow) { switch ($wipeType) { case "delete": $db->query(str_queryf("DELETE\n\t\t\t\t\t\t\tFROM run_useragent\n\t\t\t\t\t\t\tWHERE run_id = %u;", $runRow->id)); break; case "reset": $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\t\t\tSET\n\t\t\t\t\t\t\t\tstatus = 0,\n\t\t\t\t\t\t\t\tcompleted = 0,\n\t\t\t\t\t\t\t\tresults_id = NULL,\n\t\t\t\t\t\t\t\tupdated = %s\n\t\t\t\t\t\t\tWHERE run_id = %u;", swarmdb_dateformat(SWARM_NOW), $runRow->id)); break; case "cancel": $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\t\t\tSET\n\t\t\t\t\t\t\t\tstatus = 3,\n\t\t\t\t\t\t\t\tupdated = %s\n\t\t\t\t\t\t\tWHERE run_id = %u \n\t\t\t\t\t\t\t\tAND status = 0;", swarmdb_dateformat(SWARM_NOW), $runRow->id)); break; } } } // This should be outside the if for $runRows, because jobs // can sometimes be created without any runs (by accidently). // Those should be deletable as well, thus this has to be outside the loop. // Also, no need to do this in a loop, just delete them all in one query. if ($wipeType === "delete") { $db->query(str_queryf("DELETE\n\t\t\t\tFROM runs\n\t\t\t\tWHERE job_id = %u;", $jobID)); $db->query(str_queryf("DELETE\n\t\t\t\tFROM jobs\n\t\t\t\tWHERE id = %u;", $jobID)); } $this->setData(array("jobID" => $jobID, "type" => $wipeType, "result" => "ok")); }
/** * @actionMethod POST: Required. * @actionParam string jobName: May contain HTML. * @actionParam int runMax * @actionParam array runNames * @actionParam array runUrls * @actionParam array browserSets * @actionAuth: Required. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $projectID = $this->doRequireAuth(); if (!$projectID) { return; } $jobId = $request->getInt("job_id"); $testName = $request->getVal("test_name"); $uaId = $request->getVal("ua_id"); $db->query(str_queryf("LOCK TABLES runs WRITE;")); $runId = $db->getOne(str_queryf('SELECT id FROM runs WHERE job_id = %u AND name = %s ORDER BY id DESC LIMIT 1;', $jobId, $testName)); if (!$runId) { // Create this run $isInserted = $db->query(str_queryf("INSERT INTO runs (job_id, name, url, created)\n VALUES(%u, %s, %s, %s);", $jobId, $testName, "http://localhost", swarmdb_dateformat(SWARM_NOW))); $runId = $db->getInsertId(); } $db->query(str_queryf("UNLOCK TABLES;")); if (!$runId) { $this->setError("internal-error", "Could not get or create run id"); return; } $db->query(str_queryf("LOCK TABLES clients WRITE;")); $clientId = $db->getOne(str_queryf('SELECT id FROM clients WHERE useragent_id = %s LIMIT 1;', $uaId)); if (!$clientId) { $isNew = true; $isInserted = $db->query(str_queryf("INSERT INTO clients (name, useragent_id, useragent, ip, updated, created)\n VALUES(%s, %s, %s, %s, %s, %s);", $uaId, $uaId, "SauceLabs", "123.456.789.000", swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $clientId = $db->getInsertId(); } $db->query(str_queryf("UNLOCK TABLES;")); $resultInserted = $db->query(str_queryf('INSERT INTO runresults (run_id, client_id, status, store_token, updated, created) VALUES(%u, %u, 1, %s, %s, %s);', $runId, $clientId, 0, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $runresultsId = $db->getInsertId(); $isInserted = $db->query(str_queryf("INSERT INTO run_useragent (run_id, useragent_id, max, results_id, updated, created)\n VALUES(%u, %s, %u, %u, %s, %s);", $runId, $uaId, 1, $runresultsId, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $newRunUAId = $db->getInsertId(); $this->setData(array("resultsId" => $runresultsId, "runUAId" => $newRunUAId)); }
/** * @requestParam browserSet string: Show useragents from a specific * browserset only. * @requestParam onlyactive bool: If true, only user agents that * have online clients and/or pending runs are included. * If both "browserSet" and "onlyactive" are used, the overlaping * subset will be output. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $showOnlyactive = $request->getBool('onlyactive'); $filterBrowserSet = $request->getVal('browserSet', false); $data = array('userAgents' => array()); $uaIndex = BrowserInfo::getSwarmUAIndex(); foreach ($uaIndex as $uaID => $uaData) { if ($filterBrowserSet && isset($conf->browserSets->{$filterBrowserSet}) && !in_array($uaID, $conf->browserSets->{$filterBrowserSet})) { continue; } // Count online clients with this UA $clients = $db->getOne(str_queryf('SELECT COUNT(id) FROM clients WHERE useragent_id = %s AND updated > %s', $uaID, swarmdb_dateformat(time() - ($conf->client->pingTime + $conf->client->pingTimeMargin)))); $clients = intval($clients); // Count active runs for this UA $activeRuns = $db->getOne(str_queryf('SELECT COUNT(*) FROM run_useragent WHERE useragent_id = %s AND status = 1;', $uaID)); $activeRuns = intval($activeRuns); // Count pending runs for this UA $pendingRuns = $db->getOne(str_queryf('SELECT COUNT(*) FROM run_useragent WHERE useragent_id = %s AND status = 0 AND completed = 0;', $uaID)); $pendingRuns = intval($pendingRuns); // Count past runs that can still be re-run to // possibly fix non-passing results $pendingReRuns = $db->getOne(str_queryf('SELECT COUNT(*) FROM run_useragent WHERE useragent_id = %s AND status = 0 AND completed > 0;', $uaID)); $pendingReRuns = intval($pendingReRuns); if ($showOnlyactive && !$clients && !$activeRuns && !$pendingRuns && !$pendingReRuns) { continue; } $data['userAgents'][$uaID] = array('data' => $uaData, 'stats' => array('onlineClients' => $clients, 'activeRuns' => $activeRuns, 'pendingRuns' => $pendingRuns, 'pendingReRuns' => $pendingReRuns)); } $this->setData($data); }
public function doWipeJobs($wipeType, array $jobIDs, $batchSize = 100) { $db = $this->getContext()->getDB(); $stats = array('jobs' => 0, 'runs' => 0, 'run_useragent' => 0, 'runresults' => 0); $allRunRows = $db->getRows(str_queryf('SELECT id FROM runs WHERE job_id IN %l;', $jobIDs)); if ($allRunRows) { $chunks = array_chunk($allRunRows, $batchSize); foreach ($chunks as $runRows) { $runIDs = array_map(function ($row) { return $row->id; }, $runRows); if ($wipeType === 'delete') { $db->query(str_queryf('DELETE FROM run_useragent WHERE run_id in %l;', $runIDs)); } elseif ($wipeType === 'reset') { $db->query(str_queryf('UPDATE run_useragent SET status = 0, completed = 0, results_id = NULL, updated = %s WHERE run_id in %l;', swarmdb_dateformat(SWARM_NOW), $runIDs)); } $stats['run_useragent'] += $db->getAffectedRows(); if ($wipeType === 'delete') { $db->query(str_queryf('DELETE FROM runresults WHERE run_id in %l;', $runIDs)); $stats['runresults'] += $db->getAffectedRows(); } } } // This should be outside the if for $allRunRows, because jobs // can sometimes be created without any runs (by accident). // Those should be deletable as well, thus this has to be outside the loop. // Also, no need to do this in a loop, just delete them all in one query. if ($wipeType === 'delete') { $db->query(str_queryf('DELETE FROM runs WHERE job_id IN %l;', $jobIDs)); $stats['runs'] += $db->getAffectedRows(); $db->query(str_queryf('DELETE FROM jobs WHERE id IN %l;', $jobIDs)); $stats['jobs'] += $db->getAffectedRows(); } return $stats; }
/** * @requestParam "item" integer: job id * @requestParam "type" string: one of 'delete', 'reset' */ public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $jobID = $request->getInt("job_id"); $wipeType = $request->getVal("type"); if (!$jobID || !$wipeType) { $this->setError("missing-parameters"); return; } if (!in_array($wipeType, array("delete", "reset"))) { $this->setError("invalid-input"); return; } $jobOwner = $db->getOne(str_queryf("SELECT\n\t\t\t\tusers.name as user_name\n\t\t\tFROM jobs, users\n\t\t\tWHERE jobs.id = %u\n\t\t\tAND users.id = jobs.user_id\n\t\t\tLIMIT 1;", $jobID)); if (!$jobOwner) { // Job row by this ID didn't exist $this->setError("invalid-input"); return; } // Check authentication if ($request->getSessionData("auth") !== "yes" || $request->getSessionData("username") !== $jobOwner) { $this->setError("requires-auth"); return; } $runRows = $db->getRows(str_queryf("SELECT\n\t\t\t\tid\n\t\t\tFROM\n\t\t\t\truns\n\t\t\tWHERE runs.job_id = %u;", $jobID)); // Put this outside the if for runRows, // otherwise bogus jobs with 0 runs can't be deleted if ($wipeType === "delete") { $db->query(str_queryf("DELETE\n\t\t\t\tFROM run_client\n\t\t\t\tWHERE run_id in (\n\t\t\t\t\tSELECT id\n\t\t\t\t\tFROM runs\n\t\t\t\t\tWHERE job_id = %u\n\t\t\t\t);", $jobID)); $db->query(str_queryf("DELETE\n\t\t\t\tFROM run_useragent\n\t\t\t\tWHERE run_id in (\n\t\t\t\t\tSELECT id\n\t\t\t\t\tFROM runs\n\t\t\t\t\tWHERE job_id = %u\n\t\t\t\t);", $jobID)); $db->query(str_queryf("DELETE\n\t\t\t\tFROM runs\n\t\t\t\tWHERE job_id = %u;", $jobID)); $db->query(str_queryf("DELETE\n\t\t\t\tFROM jobs\n\t\t\t\tWHERE id = %u;", $jobID)); } if ($runRows) { foreach ($runRows as $runRow) { $db->query(str_queryf("DELETE\n\t\t\t\t\tFROM run_client\n\t\t\t\t\tWHERE run_id = %u;", $runRow->id)); if ($wipeType === "delete") { $db->query(str_queryf("DELETE\n\t\t\t\t\t\tFROM run_useragent\n\t\t\t\t\t\tWHERE run_id = %u;", $runRow->id)); } elseif ($wipeType === "reset") { $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\t\tSET\n\t\t\t\t\t\t\truns = 0,\n\t\t\t\t\t\t\tcompleted = 0,\n\t\t\t\t\t\t\tstatus = 0,\n\t\t\t\t\t\t\tupdated = %s\n\t\t\t\t\t\tWHERE run_id = %u;", swarmdb_dateformat(SWARM_NOW), $runRow->id)); } } } $this->setData(array("jobID" => $jobID, "type" => $wipeType, "result" => "ok")); }
/** * @actionMethod GET: Required. * @actionParam int run_id * @actionParam string type: one of 'specStart', 'timeoutCheck' */ public function doAction() { $request = $this->getContext()->getRequest(); $runID = $request->getInt("run_id"); $type = $request->getVal("type"); if (!$runID || !$type) { $this->setError("missing-parameters"); return; } if (!in_array($type, array("specStart", "timeoutCheck"))) { $this->setError("invalid-input"); return; } $now = time(); $db = $this->getContext()->getDB(); $result = ""; switch ($type) { case "specStart": if (!$request->wasGetted()) { $this->setError("requires-get"); return; } $beatRate = $request->getInt("beatRate"); $fail = $request->getInt("fail"); $error = $request->getInt("error"); $total = $request->getInt("total"); if (!$beatRate) { $this->setError("missing-parameters"); return; } $expected_update = $now + $beatRate; $db->query(str_queryf("UPDATE runresults\n\t\t\t\t\tSET\n\t\t\t\t\t\tfail = %u,\n\t\t\t\t\t\terror = %u,\n\t\t\t\t\t\ttotal = %u,\n\t\t\t\t\t\texpected_update = %s,\n\t\t\t\t\t\tupdated = %s\n\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\tAND status = 1\n\t\t\t\t\tAND ( expected_update IS NULL OR expected_update < %u );", $fail, $error, $total, swarmdb_dateformat($expected_update), swarmdb_dateformat($now), $runID, swarmdb_dateformat($expected_update))); $result = "ok"; break; case "timeoutCheck": if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $timeoutMargin = 10; // 10 seconds margin $timestamp = $now + $timeoutMargin; // Check if run is timedout. Null expected_update stands for not timedout. $isTimedout = (bool) $db->getOne(str_queryf("SELECT IF(expected_update IS NULL, false, expected_update > %u)\n\t\t\t\t\tFROM runresults\n\t\t\t\t\tWHERE run_id = %u;", swarmdb_dateformat($timestamp), $runID)); $result = array("testTimedout" => $isTimedout ? 'true' : 'false'); break; } $this->setData($result); }
public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $runID = $request->getInt("run_id"); $clientID = $request->getInt("client_id"); $useragentID = $request->getVal("useragent_id"); if (!$runID || !$clientID) { $this->setError("missing-parameters"); return; } $jobID = (int) $db->getOne(str_queryf("SELECT job_id FROM runs WHERE id = %u;", $runID)); if (!$jobID) { $this->setError("invalid-input", "Run {$runID} not found."); return; } $jobOwner = $db->getOne(str_queryf("SELECT\n\t\t\t\tusers.name as user_name\n\t\t\tFROM jobs, users\n\t\t\tWHERE jobs.id = %u\n\t\t\tAND users.id = jobs.user_id\n\t\t\tLIMIT 1;", $jobID)); if (!$jobOwner) { $this->setError("invalid-input", "Job {$jobID} not found."); return; } // Check authentication if ($request->getSessionData("auth") !== "yes" || $request->getSessionData("username") !== $jobOwner) { $this->setError("requires-auth"); return; } $runJobID = (int) $db->getOne(str_queryf("SELECT job_id\n\t\t\tFROM runs\n\t\t\tWHERE id = %u;", $runID)); if ($runJobID !== $jobID) { $this->setError("invalid-input", "Run {$runID} does not belong to job {$jobID}."); return; } $clientUseragentID = $db->getOne(str_queryf("SELECT useragent_id\n\t\t\tFROM clients\n\t\t\tWHERE id = %u;", $clientID)); if ($clientUseragentID !== $useragentID) { $this->setError("invalid-input", "Client {$clientID} does not run useragent {$useragentID}"); return; } $db->query(str_queryf("UPDATE\n\t\t\t\trun_useragent\n\t\t\tSET\n\t\t\t\tstatus = 0,\n\t\t\t\tcompleted = 0,\n\t\t\t\tresults_id = NULL,\n\t\t\t\tupdated = %s\n\t\t\tWHERE run_id = %u\n\t\t\tAND useragent_id = %s;", swarmdb_dateformat(SWARM_NOW), $runID, $useragentID)); $this->setData(array("jobID" => $jobID, "runID" => $runID, "clientID" => $clientID, "useragentID" => $useragentID, "result" => "ok")); }
/** * @actionMethod POST: Required. * @actionParam string jobName: May contain HTML. * @actionParam int runMax * @actionParam array runNames * @actionParam array runUrls * @actionParam array browserSets * @actionAuth: Required. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $projectID = $this->doRequireAuth(); if (!$projectID) { return; } $buildId = $request->getInt("buildId"); $jobName = $request->getVal("jobName"); if (!$jobName || !$buildId) { $this->setError("missing-parameters"); return; } // Verify job name maxlength (otherwise MySQL will crop it, which might // result in incomplete html, screwing up the JobPage). if (strlen($jobName) > 255) { $this->setError("invalid-input", "Job name too long (up to 255 characters)."); } $db->query(str_queryf("LOCK TABLES jobs WRITE;")); $jobId = $db->getOne(str_queryf('SELECT id FROM jobs WHERE build_id = %u AND project_id = %s LIMIT 1;', $buildId, $projectID)); $isNew = true; if ($jobId) { $isNew = false; } else { // Create job $isInserted = $db->query(str_queryf("INSERT INTO jobs (build_id, name, project_id, created)\n VALUES (%u, %s, %s, %s);", $buildId, $jobName, $projectID, swarmdb_dateformat(SWARM_NOW))); $jobId = $db->getInsertId(); } $db->query(str_queryf("UNLOCK TABLES;")); if (!$jobId) { $this->setError("internal-error", "Get or create of job failed."); return; } $this->setData(array("id" => $jobId, "isNew" => $isNew, "runTotal" => count($runs))); }
/** * @actionMethod POST: Required. * @actionParam run_token string * @actionParam client_id int */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $runToken = $request->getVal("run_token"); if ($conf->client->requireRunToken && !$runToken) { $this->setError("invalid-input", "This TestSwarm does not allow unauthorized clients to join the swarm."); return; } $clientID = $request->getInt("client_id"); if (!$clientID) { $this->setError("invalid-input"); return; } // Create a Client object that verifies client id, user agent and run token. // Also updates the client 'alive' timestamp. // Throws exception (caught higher up) if stuff is invalid. $client = Client::newFromContext($this->getContext(), $runToken, $clientID); // Get oldest run for this user agent, that isn't on the max yet and isn't // already ran by another client. $runID = $db->getOne(str_queryf("SELECT\n\t\t\t\trun_id\n\t\t\tFROM\n\t\t\t\trun_useragent\n\t\t\tWHERE useragent_id = %s\n\t\t\tAND runs < max\n\t\t\tAND NOT EXISTS (SELECT 1 FROM run_client WHERE run_useragent.run_id = run_id AND client_id = %u)\n\t\t\tORDER BY run_id DESC\n\t\t\tLIMIT 1;", $browserInfo->getSwarmUaID(), $clientID)); $runInfo = false; // A run was found for the current user_agent if ($runID) { $row = $db->getRow(str_queryf("SELECT\n\t\t\t\t\truns.url as run_url,\n\t\t\t\t\tjobs.name as job_name,\n\t\t\t\t\truns.name as run_name\n\t\t\t\tFROM\n\t\t\t\t\truns, jobs\n\t\t\t\tWHERE runs.id = %u\n\t\t\t\tAND jobs.id = runs.job_id\n\t\t\t\tLIMIT 1;", $runID)); if ($row->run_url && $row->job_name && $row->run_name) { # Mark the run as "in progress" on the useragent $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\truns = runs + 1,\n\t\t\t\t\t\tstatus = 1,\n\t\t\t\t\t\tupdated = %s\n\t\t\t\t\tWHERE run_id = %u\n\t\t\t\t\tAND useragent_id = %s\n\t\t\t\t\tLIMIT 1;", swarmdb_dateformat(SWARM_NOW), $runID, $browserInfo->getSwarmUaID())); # Initialize the client run $db->query(str_queryf("INSERT INTO run_client\n\t\t\t\t\t(run_id, client_id, status, updated, created)\n\t\t\t\t\tVALUES(%u, %u, 1, %s, %s);", $runID, $clientID, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $runInfo = array("id" => $runID, "url" => $row->run_url, "desc" => $row->job_name . ' ' . $row->run_name); } } $this->setData(array("confUpdate" => array("client" => $conf->client), "runInfo" => $runInfo)); }
/** * @actionNote This action takes no parameters. */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $db = $this->getContext()->getDB(); $conf = $this->getContext()->getConf(); $request = $this->getContext()->getRequest(); $resetTimedoutRuns = 0; // Get clients that are considered disconnected (not responding to the latest pings). // Then mark the runresults of its active runs as timed-out, and reset those runs so // they become available again for different clients in GetrunAction. $clientMaxAge = swarmdb_dateformat(time() - ($conf->client->pingTime + $conf->client->pingTimeMargin)); $rows = $db->getRows(str_queryf("SELECT\n\t\t\t\trunresults.id as id\n\t\t\tFROM\n\t\t\t\trunresults\n\t\t\tINNER JOIN clients ON runresults.client_id = clients.id\n\t\t\tWHERE runresults.status = 1\n\t\t\tAND clients.updated < %s;", $clientMaxAge)); if ($rows) { $resetTimedoutRuns = count($rows); foreach ($rows as $row) { // Reset the run $db->query(str_queryf("UPDATE run_useragent\n\t\t\t\t\tSET\n\t\t\t\t\t\tstatus = 0,\n\t\t\t\t\t\tresults_id = NULL\n\t\t\t\t\tWHERE results_id = %u;", $row->id)); // Update status of the result $db->query(str_queryf("UPDATE runresults\n\t\t\t\t\tSET status = %s\n\t\t\t\t\tWHERE id = %u;", ResultAction::$STATE_LOST, $row->id)); } } $this->setData(array("resetTimedoutRuns" => $resetTimedoutRuns)); }
/** * Creates the actual user, seperated from doAction to allow * make internal use easier, also use or modify the session. * @param $username string * @param $password string * @return bool */ public function doCreateUser($username, $password) { $db = $this->getContext()->getDB(); if (!$username || !$password) { $this->setError("missing-parameters"); return false; } // Validate user name (github.com/jquery/testswarm/issues/118) // Only allow lowercase a-z, 0-9 and dashed, must start with a letter if (!preg_match("/^[a-z][-a-z0-9]*\$/", $username)) { $this->setError("invalid-input", "Username may only contain lowercase a-z, 0-9 and dashes and must start with a letter."); return false; } // Verify user name maxlength (otherwise MySQL will crop it) if (strlen($username) > 255) { $this->setError("invalid-input", "User name too long (up to 255 characters)."); } // Check if this user name is already taken $row = $db->getRow(str_queryf("SELECT id FROM users WHERE name = %s;", $username)); if ($row) { $this->setError("invalid-input", "Username \"{$username}\" is already taken."); return false; } // Random between 1,000,000,000 and 9,999,999,999 $seedHash = sha1(mt_rand(1000000000, 9999999999.0)); $passwordHash = sha1($seedHash . $password); $authTokenHash = sha1(mt_rand(1000000000, 9999999999.0)); // Create the user $isInserted = $db->query(str_queryf("INSERT INTO users\n\t\t\t(name, updated, created, seed, password, auth)\n\t\t\tVALUES(%s, %s, %s, %s, %s, %s);", $username, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW), $seedHash, $passwordHash, $authTokenHash)); $newUserId = $db->getInsertId(); if (!$isInserted || !$newUserId) { $this->setError("internal-error", "Insertion of user into database failed."); return false; } $this->setData(array("status" => "logged-in", "username" => $username, "userID" => $newUserId)); return true; }
/** * @actionMethod POST: Required. * @actionParam string jobName: May contain HTML. * @actionParam int runMax * @actionParam array runNames * @actionParam array runUrls * @actionParam array browserSets * @actionAuth: Required. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $projectID = $this->doRequireAuth(); if (!$projectID) { return; } $uaId = $request->getVal("ua_id"); $isNew = false; $clientId = $db->getOne(str_queryf('SELECT id FROM clients WHERE useragent_id = %s LIMIT 1;', $uaId)); if (!$clientId) { $isNew = true; $isInserted = $db->query(str_queryf("INSERT INTO clients (name, useragent_id, useragent, ip, updated, created)\n VALUES(%s, %s, %s, %s, %s, %s);", $uaId, $uaId, "SauceLabs", "123.456.789.000", swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $clientId = $db->getInsertId(); } $this->setData(array("clientId" => $clientId, "isNew" => $isNew)); }
protected function update() { $db = $this->getContext()->getDB(); $id = $this->getOption('id'); $displayTitle = $this->getOption('display-title'); $siteUrl = $this->getOption('site-url'); if (!$id) { $this->error('--id is required.'); } // Check if this project exists. $field = $db->getOne(str_queryf('SELECT id FROM projects WHERE id = %s;', $id)); if (!$field) { $this->error('Project does not exist. Set --create to create a project.'); } if (!$displayTitle && !$siteUrl) { $this->error('Unable to perform update. No values provided.'); } if ($displayTitle) { $isUpdated = $db->query(str_queryf('UPDATE projects SET display_title = %s, updated = %s WHERE id = %s;', $displayTitle, swarmdb_dateformat(SWARM_NOW), $id)); if (!$isUpdated) { $this->error('Failed to update database.'); } } if ($siteUrl) { $isUpdated = $db->query(str_queryf('UPDATE projects SET site_url = %s, updated = %s WHERE id = %s;', $siteUrl, swarmdb_dateformat(SWARM_NOW), $id)); if (!$isUpdated) { $this->error('Failed to update database.'); } } $this->out('Project has been updated.'); }
/** * @param string $id * @param array $options * @return array Exposes the new auth token */ public function create($id, array $options = null) { $db = $this->getContext()->getDB(); $password = isset($options['password']) ? $options['password'] : null; $displayTitle = isset($options['displayTitle']) ? $options['displayTitle'] : null; $siteUrl = isset($options['siteUrl']) ? $options['siteUrl'] : ''; if (!$id || !$displayTitle || !$password) { $this->setError('missing-parameters'); return; } // Check if a project by this id doesn't exist already $row = $db->getOne(str_queryf('SELECT id FROM projects WHERE id = %s;', $id)); if ($row) { $this->setError('invalid-input', 'Unable to create project, a project by that name exists already.'); return; } // Validate project id if (!LoginAction::isValidName($id)) { $this->setError('invalid-input', 'Project ids must be in format: "' . LoginAction::getNameValidationRegex() . '".'); return; } // maxlength (otherwise MySQL will crop it) if (strlen($displayTitle) > 255) { $this->setError('Display title has to be no longer than 255 characters.'); return; } // Create the project $authToken = LoginAction::generateRandomHash(40); $authTokenHash = sha1($authToken); $isInserted = $db->query(str_queryf('INSERT INTO projects (id, display_title, site_url, password, auth_token, updated, created) VALUES(%s, %s, %s, %s, %s, %s, %s);', $id, $displayTitle, $siteUrl, LoginAction::generatePasswordHash($password), $authTokenHash, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); if (!$isInserted) { $this->setError('internal-error', 'Insertion of row into database failed.'); return; } return array('authToken' => $authToken); }
public function doAction() { $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $userName = $request->getVal("item"); if (!$userName) { $this->setError("missing-parameters"); return; } $userID = $db->getOne(str_queryf("SELECT id FROM users WHERE name = %s;", $userName)); $userID = intval($userID); if (!$userID) { $this->setError("invalid-input", "User does not exist"); return; } $uaIndex = BrowserInfo::getSwarmUAIndex(); // Active clients $activeClients = array(); $clientRows = $db->getRows(str_queryf("SELECT\n\t\t\t\tuseragent_id,\n\t\t\t\tuseragent,\n\t\t\t\tcreated\n\t\t\tFROM\n\t\t\t\tclients\n\t\t\tWHERE user_id = %u\n\t\t\tAND updated > %s\n\t\t\tORDER BY created DESC;", $userID, swarmdb_dateformat(strtotime("1 minutes ago")))); if ($clientRows) { foreach ($clientRows as $clientRow) { $bi = BrowserInfo::newFromContext($this->getContext(), $clientRow->useragent); $activeClient = array("uaID" => $clientRow->useragent_id, "uaRaw" => $bi->getRawUA(), "uaData" => $bi->getSwarmUaItem(), "uaBrowscap" => $bi->getBrowscap()); self::addTimestampsTo($activeClient, $clientRow->created, "connected"); $activeClients[] = $activeClient; } } // Recent jobs $recentJobs = array(); // List of all user agents used in recent jobs // This is as helper allow creating proper gaps when iterating // over jobs. $userAgents = array(); $jobRows = $db->getRows(str_queryf("SELECT\n\t\t\t\tid,\n\t\t\t\tname\n\t\t\tFROM\n\t\t\t\tjobs\n\t\t\tWHERE jobs.user_id = %u\n\t\t\tORDER BY jobs.created DESC\n\t\t\tLIMIT 15;", $userID)); if ($jobRows) { $uaRunStatusStrength = array_flip(array("passed", "new", "progress", "failed", "timedout", "error")); foreach ($jobRows as $jobRow) { $jobID = intval($jobRow->id); $jobActionContext = $this->getContext()->createDerivedRequestContext(array("action" => "job", "item" => $jobID), "GET"); $jobAction = JobAction::newFromContext($jobActionContext); $jobAction->doAction(); if ($jobAction->getError()) { $this->setError($jobAction->getError()); return; } $jobActionData = $jobAction->getData(); // Add user agents array of this job to the overal user agents list. // php array+ automatically fixes clashing keys. The values are always the same // so it doesn't matter whether or not it overwrites. $userAgents += $jobActionData["userAgents"]; // The summerized status for each user agent run // of this job. e.g. if all are new except one, // then it will be on "progress", if all are complete // then the worst failure is put in the summary $uaSummary = array(); $uaNotNew = array(); $uaHasIncomplete = array(); $uaStrongestStatus = array(); foreach ($jobActionData["runs"] as $run) { foreach ($run["uaRuns"] as $uaID => $uaRun) { if ($uaRun["runStatus"] !== "new" && !in_array($uaID, $uaNotNew)) { $uaNotNew[] = $uaID; } if ($uaRun["runStatus"] === "new" || $uaRun["runStatus"] === "progress") { if (!in_array($uaID, $uaHasIncomplete)) { $uaHasIncomplete[] = $uaID; } } if (!isset($uaStrongestStatus[$uaID]) || $uaRunStatusStrength[$uaRun["runStatus"]] > $uaRunStatusStrength[$uaStrongestStatus[$uaID]]) { $uaStrongestStatus[$uaID] = $uaRun["runStatus"]; } $uaSummary[$uaID] = !in_array($uaID, $uaNotNew) ? "new" : (in_array($uaID, $uaHasIncomplete) ? "progress" : $uaStrongestStatus[$uaID]); } } $recentJobs[] = array("id" => $jobID, "name" => $jobRow->name, "url" => swarmpath("job/{$jobID}", "fullurl"), "uaSummary" => $uaSummary); } } natcaseksort($userAgents); $this->setData(array("userName" => $userName, "activeClients" => $activeClients, "recentJobs" => $recentJobs, "uasInJobs" => $userAgents)); }
/** * @actionMethod POST: Required. * @actionParam run_token string * @actionParam client_id int */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError("requires-post"); return; } $runToken = $request->getVal("run_token"); if ($conf->client->requireRunToken && !$runToken) { $this->setError("missing-parameters", "This TestSwarm does not allow unauthorized clients to join the swarm."); return; } $clientID = $request->getInt("client_id"); if (!$clientID) { $this->setError("missing-parameters"); return; } // Create a Client object that verifies client id, user agent and run token. // Also updates the client 'alive' timestamp. // Throws exception (caught higher up) if stuff is invalid. $client = Client::newFromContext($this->getContext(), $runToken, $clientID); // Get oldest idle (status=0) run for this user agent. // Except if it was already ran in this client in the past (client_id=%u), because // in that case it must've failed. We don't want it to run in the same client again. $runID = $db->getOne(str_queryf('SELECT run_id FROM run_useragent WHERE useragent_id = %s AND status = 0 AND NOT EXISTS (SELECT 1 FROM runresults WHERE runresults.run_id = run_useragent.run_id AND runresults.client_id = %u) ORDER BY run_id DESC LIMIT 1;', $browserInfo->getSwarmUaID(), $clientID)); $runInfo = false; // A run was found for the current user_agent if ($runID) { $row = $db->getRow(str_queryf("SELECT\n\t\t\t\t\truns.url as run_url,\n\t\t\t\t\tjobs.name as job_name,\n\t\t\t\t\truns.name as run_name\n\t\t\t\tFROM\n\t\t\t\t\truns, jobs\n\t\t\t\tWHERE runs.id = %u\n\t\t\t\tAND jobs.id = runs.job_id\n\t\t\t\tLIMIT 1;", $runID)); if ($row->run_url && $row->job_name && $row->run_name) { // Create stub runresults entry $storeToken = sha1(mt_rand()); $isInserted = $db->query(str_queryf('INSERT INTO runresults (run_id, client_id, status, store_token, updated, created) VALUES(%u, %u, 1, %s, %s, %s);', $runID, $clientID, sha1($storeToken), swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); $runresultsId = $db->getInsertId(); if (!$isInserted || !$runresultsId) { $this->setError('internal-error', 'Creation of runresults database entry failed.'); return false; } // Mark as in-progress (status=1), and link runresults entry $db->query(str_queryf('UPDATE run_useragent SET status = 1, updated = %s, results_id = %u WHERE run_id = %u AND useragent_id = %s LIMIT 1;', swarmdb_dateformat(SWARM_NOW), $runresultsId, $runID, $browserInfo->getSwarmUaID())); $runInfo = array("id" => $runID, "url" => $row->run_url, "desc" => $row->job_name . ' ' . $row->run_name, 'resultsId' => $runresultsId, 'resultsStoreToken' => $storeToken); } } $this->setData(array('runInfo' => $runInfo)); }
/** * @actionMethod POST: Required. * @actionParam jobName string: May contain HTML. * @actionParam runMax int * @actionParam runNames array * @actionParam runUrls array * @actionParam browserSets array * @actionParam string authUsername * @actionParam string authToken * @actionAuth: Yes. */ public function doAction() { $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); $userId = $this->doRequireAuth(); if (!$userId) { return; } $jobName = $request->getVal("jobName"); $runMax = $request->getInt("runMax"); $runNames = $request->getArray("runNames"); $runUrls = $request->getArray("runUrls"); $browserSets = $request->getArray("browserSets"); if (!$jobName || !$runNames || !count($runNames) || !$runUrls || !count($runUrls) || !$browserSets || !count($browserSets)) { $this->setError("missing-parameters"); return; } if ($runMax < 1 || $runMax > 99) { $this->setError("invalid-input", "runMax must be a number between 1 and 99."); return; } $runs = array(); // Loop through runNames / runUrls to validate them ahead of time, // and filter out empty ones from the AddjobPage. foreach ($runNames as $runNr => $runName) { if (!isset($runUrls[$runNr])) { $this->setError("invalid-input", "One or more runs is missing a URL."); return; } $runUrl = $runUrls[$runNr]; // Filter out empty submissions, // AddjobPage may submit more input fields then filled in if ($runUrl == '' && $runName == '') { continue; } if ($runUrl == '' || $runName == '') { $this->setError("invalid-input", "Run names and urls must be non-empty."); return; } if (strlen($runName) > 255) { $formRunNr = $runNr + 1; // offset 0 $this->setError("invalid-input", "Run #{$formRunNr} name was too long (up to 255 characters)."); return; } $runs[] = array("name" => $runName, "url" => $runUrl); } if (!count($runs)) { $this->setError('missing-parameters', 'Job must have atleast 1 run.'); return; } // Generate a list of user agent IDs based on the selected browser sets $browserSetsCnt = count($browserSets); $browserSets = array_unique($browserSets); if ($browserSetsCnt !== count($browserSets)) { $this->setError("invalid-input", "Duplicate entries in browserSets parameter."); return; } $swarmUaIndex = BrowserInfo::getSwarmUAIndex(); $uaIDs = array(); foreach ($browserSets as $browserSet) { if (!isset($conf->browserSets->{$browserSet})) { $this->setError("invalid-input", "Unknown browser set: {$browserSet}."); return; } // Merge the arrays, and re-index with unique (also prevents duplicate entries) $uaIDs = array_unique(array_merge($uaIDs, $conf->browserSets->{$browserSet})); } if (!count($uaIDs)) { $this->setError("data-corrupt", "No user agents matched the generated browserset filter."); return; } // Verify job name maxlength (otherwise MySQL will crop it, which might // result in incomplete html, screwing up the JobPage). if (strlen($jobName) > 255) { $this->setError("invalid-input", "Job name too long (up to 255 characters)."); } // Create job $isInserted = $db->query(str_queryf("INSERT INTO jobs (user_id, name, created)\n\t\t\tVALUES (%u, %s, %s);", $userId, $jobName, swarmdb_dateformat(SWARM_NOW))); $newJobId = $db->getInsertId(); if (!$isInserted || !$newJobId) { $this->setError("internal-error", "Insertion of job into database failed."); return; } // Create all runs and schedule them for the wanted browsersets in run_useragent foreach ($runs as $run) { // Create this run $isInserted = $db->query(str_queryf("INSERT INTO runs (job_id, name, url, created)\n\t\t\t\tVALUES(%u, %s, %s, %s);", $newJobId, $run['name'], $run['url'], swarmdb_dateformat(SWARM_NOW))); $newRunId = $db->getInsertId(); if (!$isInserted || !$newRunId) { $this->setError("internal-error", "Insertion of job into database failed."); return; } // Schedule run_useragent entries for all user agents matching // the browerset(s) for this job. foreach ($uaIDs as $uaID) { $isInserted = $db->query(str_queryf("INSERT INTO run_useragent (run_id, useragent_id, max, updated, created)\n\t\t\t\t\tVALUES(%u, %s, %u, %s, %s);", $newRunId, $uaID, $runMax, swarmdb_dateformat(SWARM_NOW), swarmdb_dateformat(SWARM_NOW))); } } $this->setData(array("id" => $newJobId, "runTotal" => count($runs), "uaTotal" => count($uaIDs))); }
/** * The actual database updates * Friendly reminder from http://dev.mysql.com/doc/refman/5.1/en/alter-table.html * - Column name must be mentioned twice in ALTER TABLE CHANGE * - Definition must be complete * So 'CHANGE foo BIGINT' on a 'foo INT UNSIGNED DEFAULT 1' will remove * the default and unsigned property. * Except for PRIMARY KEY or UNIQUE properties, those must never be * part of a CHANGE clause. */ protected function doDatabaseUpdates() { if ($this->getContext()->dbLock()) { $this->error('Database is currently locked, please remove ./cache/database.lock before updating.'); } $db = $this->getContext()->getDB(); $this->out('Setting database.lock, other requests may not access the database during the update.'); $this->getContext()->dbLock(true); $this->out('Running tests on the database to detect which updates are needed.'); /** * 0.2.0 -> 1.0.0-alpha (patch-new-ua-runresults.sql) * useragents and run_client table removed, many column changes, new runresults table. */ // If the previous version was before 1.0.0 we won't offer an update, because most // changes in 1.0.0 can't be simulated without human intervention. The changes are not // backwards compatible. Instead do a few quick checks to verify this is in fact a // pre-1.0.0 database, then ask the user for a re-install from scratch // (except for the users table). $has_run_client = $db->tableExists('run_client'); $has_users_request = $db->fieldExists('users', 'request'); $clients_useragent_id = $db->fieldInfo('clients', 'useragent_id'); if (!$clients_useragent_id) { $this->unknownDatabaseState('clients.useragent_id not found'); return; } if (!$has_run_client && !$has_users_request && !$clients_useragent_id->numeric && $clients_useragent_id->type === 'string') { $this->out('... run_client table already dropped'); $this->out('... users.request already dropped'); $this->out('... client.useragent_id is up to date'); } else { $this->out("\n" . "It appears this database is from before 1.0.0. No update exists for those versions.\n" . "The updater could re-install TestSwarm (optionally preserving user accounts)\n" . "THIS WILL DELETE ALL DATA.\nContinue? (Y/N)"); $reinstall = $this->cliInput(); if ($reinstall !== 'Y') { // Nothing left to do. Remove database.lock and abort the script $this->getContext()->dbLock(false); return; } $this->out("Import user names and tokens from the old database after re-installing?\n" . "(Note: password and seed cannot be restored due to incompatibility in the database.\n" . ' Instead the auth token will be used as the the new password) (Y/N)'); $reimportUsers = $this->cliInput(); // Drop all known TestSwarm tables in the database // (except users, handled separately) foreach (array('run_client', 'clients', 'run_useragent', 'useragents', 'runs', 'jobs') as $dropTable) { $this->outRaw("Dropping {$dropTable} table..."); $exists = $db->tableExists($dropTable); if ($exists) { $dropped = $db->query('DROP TABLE ' . $db->addIdentifierQuotes($dropTable)); $this->out(' ' . ($dropped ? 'OK' : 'FAILED')); } else { $this->out('SKIPPED (didn\'t exist)'); } } // Handle users table (reimport or drop as well) $userRows = array(); if ($reimportUsers === 'Y') { $this->out('Upgrading users table'); $this->outRaw('Fetching current users...'); $has_users = $db->tableExists('users'); if (!$has_users) { $this->out('SKIPPED (users table didn\'t exist)'); } else { $userRows = $db->getRows('SELECT * FROM users'); $this->out('OK'); } } $this->outRaw('Dropping users table...'); $dropped = $db->query('DROP TABLE users'); $this->out(' ' . ($dropped ? 'OK' : 'FAILED')); // Create new tables $this->outRaw('Creating new tables... (this may take a few minutes)'); global $swarmInstallDir; $fullSchemaFile = "{$swarmInstallDir}/config/tables.sql"; if (!is_readable($fullSchemaFile)) { $this->error('Can\'t read schema file'); } $fullSchemaSql = file_get_contents($fullSchemaFile); $executed = $db->batchQueryFromFile($fullSchemaSql); if (!$executed) { $this->error('Creating new tables failed'); } $this->out('OK'); if ($reimportUsers === 'Y') { $this->out('Re-importing ' . count($userRows) . ' users...'); foreach ($userRows as $userRow) { $this->outRaw('- creating user "' . $userRow->name . '"... '); if (empty($userRow->password) || empty($userRow->seed) || empty($userRow->auth)) { $this->out('SKIPPED: Not a project account but a swarm client.'); continue; } try { $signupAction = SignupAction::newFromContext($this->getContext()); // Password stored in the old database is a hash of the old seed (of type 'double' // and the actual password. We can't create this user with the same password because // sha1 is not supposed to be decodable. // I tried overriding the created row after the creation with the old seed and password, // but that didn't work because the old seed doesn't fit in the new seed field (of binary(40)). // When inserted, mysql transforms it into something else and sha1(seed + password) will no // longer match the hash. So instead create the new user with the auth token as password. $signupAction->doCreateUser($userRow->name, $userRow->auth); $err = $signupAction->getError(); if (!$err) { $this->outRaw('OK. Restoring auth token... '); $data = $signupAction->getData(); $updated = $db->query(str_queryf('UPDATE users SET auth = %s WHERE id = %u', $userRow->auth, $data['userID'])); $this->out($updated ? 'OK.' : 'FAILED.'); } else { $this->out("FAILED. SignupAction error. {$err['info']}"); } } catch (Exception $e) { $this->out("FAILED. Unexpected exception thrown while creating account. {$e->getMessage()}"); } } } // End of users re-import } // End of patch-new-ua-runresults.sql /** * 1.0.0-alpha (patch-users-projects-conversion.sql) * users table removed, new projects table, various column changes. */ $has_users = $db->tableExists('users'); $has_clients_user_id = $db->fieldInfo('clients', 'user_id'); $has_jobs_user_id = $db->fieldInfo('jobs', 'user_id'); $has_projects = $db->tableExists('projects'); $has_clients_name = $db->fieldInfo('clients', 'name'); $has_jobs_project_id = $db->fieldInfo('jobs', 'project_id'); $has_clients_useragent_id = $db->fieldInfo('clients', 'useragent_id'); if (!$has_users && !$has_clients_user_id && !$has_jobs_user_id) { $this->out('... users table already dropped'); $this->out('... clients.user_id already dropped'); $this->out('... jobs.user_id already dropped'); } else { // Verify that the entire database is in the 1.0.0-alpha2012 state, // not just part of it. foreach (array('users table' => $has_users, 'clients.user_id' => $has_clients_user_id, 'jobs.user_id' => $has_jobs_user_id, 'projects table' => !$has_projects, 'clients.name' => !$has_clients_name, 'jobs.project_id' => !$has_jobs_project_id, 'clients.useragent_id' => $has_clients_useragent_id) as $label => $isAsExpected) { if (!$isAsExpected) { $this->unknownDatabaseState($label . ' not found'); return; } } $this->out('Schema changes before users-projects-conversion migration...'); $this->out('... creating projects table'); $db->query("CREATE TABLE `projects` (\n `id` varchar(255) binary NOT NULL PRIMARY KEY,\n `display_title` varchar(255) binary NOT NULL,\n `site_url` blob NOT NULL default '',\n `password` tinyblob NOT NULL,\n `auth_token` tinyblob NOT NULL,\n `updated` binary(14) NOT NULL,\n `created` binary(14) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); $this->out('... adding clients.name'); $db->query("ALTER TABLE clients\n ADD `name` varchar(255) binary NOT NULL AFTER `id`"); $this->out('... adding jobs.project_id'); $db->query("ALTER TABLE jobs\n ADD `project_id` varchar(255) binary NOT NULL AFTER `name`"); $this->out('... dropping constraint fk_clients_user_id'); $db->query("ALTER TABLE clients\n DROP FOREIGN KEY fk_clients_user_id"); $this->out('... dropping constraint fk_jobs_user_id'); $db->query("ALTER TABLE jobs\n DROP FOREIGN KEY fk_jobs_user_id"); $this->out('... dropping constraint fk_runs_job_id'); $db->query("ALTER TABLE runs\n DROP FOREIGN KEY fk_runs_job_id"); $this->out('... dropping constraint fk_run_useragent_run_id'); $db->query("ALTER TABLE run_useragent\n DROP FOREIGN KEY fk_run_useragent_run_id"); $this->out('... dropping constraint fk_runresults_client_id'); $db->query("ALTER TABLE runresults\n DROP FOREIGN KEY fk_runresults_client_id"); $this->out('... dropping index idx_users_name'); $db->query("ALTER TABLE users\n DROP INDEX idx_users_name"); $this->out('... dropping index idx_clients_user_useragent_updated'); $db->query("ALTER TABLE clients\n DROP INDEX idx_clients_user_useragent_updated"); $this->out('... dropping index idx_jobs_user'); $db->query("ALTER TABLE jobs\n DROP INDEX idx_jobs_user"); $this->out('Migrating old content into new schema...'); $this->out('... fetching users table'); $userRows = $db->getRows('SELECT * FROM users') ?: array(); $this->out('... found ' . count($userRows) . ' users'); foreach ($userRows as $userRow) { $this->out('... creating project "' . $userRow->name . '"'); if (!trim($userRow->seed) || !trim($userRow->password) || !trim($userRow->auth)) { // Client.php used to create rows in the users table with blanks // in these "required" fields. MySQL expands the emptyness to the full // 40-width of the column. Hence the trim(). $this->out(' SKIPPED: Not a project account but a swarm client.'); continue; } // Validate project id if (!LoginAction::isValidName($userRow->name)) { $this->out(' SKIPPED: User name not a valid project id. Must match: ' . LoginAction::getNameValidationRegex()); continue; } if (!$db->getOne(str_queryf('SELECT 1 FROM jobs WHERE user_id=%u', $userRow->id))) { $this->out(' SKIPPED: Account has 0 jobs'); continue; } $isInserted = $db->query(str_queryf('INSERT INTO projects (id, display_title, site_url, password, auth_token, updated, created) VALUES(%s, %s, %s, %s, %s, %s, %s);', $userRow->name, $userRow->name, '', LoginAction::generatePasswordHashForUserrow($userRow), sha1($userRow->auth), swarmdb_dateformat(SWARM_NOW), $userRow->created)); if (!$isInserted) { $this->out(' FAILED: Failed to insert row into projects table.'); continue; } $this->out('... updating references for project "' . $userRow->name . '"'); $isUpdated = $db->query(str_queryf('UPDATE clients SET name=%s WHERE user_id=%u', $userRow->name, $userRow->id)); if (!$isUpdated) { $this->out(' FAILED: Failed to update rows in clients table.'); continue; } $isUpdated = $db->query(str_queryf('UPDATE jobs SET project_id=%s WHERE user_id=%u', $userRow->name, $userRow->id)); if (!$isUpdated) { $this->out(' FAILED: Failed to update rows in jobs table.'); continue; } } $this->out('Schema changes after users-projects-conversion migration...'); $this->out('... changing clients.useragent_id'); $db->query("ALTER TABLE clients\n CHANGE COLUMN `useragent_id` `useragent_id` varchar(255) NOT NULL"); $this->out('... dropping clients.user_id'); $db->query("ALTER TABLE clients\n DROP COLUMN `user_id`"); $this->out('... dropping jobs.user_id'); $db->query("ALTER TABLE jobs\n DROP COLUMN `user_id`"); $this->out('... dropping users table'); $db->query("DROP TABLE users"); $this->out('... adding index idx_clients_name_ua_created'); $db->query("ALTER TABLE clients\n ADD INDEX idx_clients_name_ua_created (name, useragent_id, created);"); $this->out('... adding index idx_jobs_project_created'); $db->query("ALTER TABLE jobs\n ADD INDEX idx_jobs_project_created (project_id, created);"); } // End of patch-users-projects-conversion.sql $this->getContext()->dbLock(false); $this->out("Removed database.lock.\nNo more updates."); }
/** * @actionMethod POST: Required. * @actionParam int client_id * @actionParam string run_token * @actionParam int run_id * @actionParam string results_id * @actionParam string results_store_token * @actionParam int total * @actionParam int fail * @actionParam int error * @actionParam int status: `runresults.status` * @actionParam string report_html: HTML snapshot of the test results page. */ public function doAction() { $browserInfo = $this->getContext()->getBrowserInfo(); $conf = $this->getContext()->getConf(); $db = $this->getContext()->getDB(); $request = $this->getContext()->getRequest(); if (!$request->wasPosted()) { $this->setError('requires-post'); return; } $runToken = $request->getVal('run_token'); if ($conf->client->requireRunToken && !$runToken) { $this->setError('missing-parameters', 'This TestSwarm does not allow unauthorized clients to join the swarm.'); return; } $runID = $request->getInt('run_id'); $clientID = $request->getInt('client_id'); $resultsID = $request->getVal('results_id'); $resultsStoreToken = $request->getVal('results_store_token'); if (!$runID || !$clientID || !$resultsID || !$resultsStoreToken) { $this->setError('missing-parameters'); return; } // Create a Client object that verifies client id, user agent and run token. // Also updates the client 'alive' timestamp. // Throws exception (caught higher up) if stuff is invalid. $client = Client::newFromContext($this->getContext(), $runToken, $clientID); $total = $request->getInt('total', 0); $fail = $request->getInt('fail', 0); $error = $request->getInt('error', 0); $status = $request->getInt('status', 2); $reportHtml = $request->getVal('report_html', ''); if (!in_array($status, array(2, 3))) { $this->setError('invalid-input', 'Illegal status to be set from the client side in action=saverun.'); return; } // Verify this runresults row exists, // also naturally validates run_id and store_token $res = $db->query(str_queryf('SELECT id FROM runresults WHERE id = %u AND run_id = %u AND store_token = %s;', $resultsID, $runID, sha1($resultsStoreToken))); if (!$res || $db->getNumRows($res) !== 1) { $this->setError('invalid-input'); return; } $db->query(str_queryf('UPDATE runresults SET status = %u, total = %u, fail = %u, error = %u, report_html = %s, updated = %s WHERE id = %u LIMIT 1;', $status, $total, $fail, $error, gzencode($reportHtml), swarmdb_dateformat(SWARM_NOW), $resultsID)); if ($db->getAffectedRows() !== 1) { $this->setError('internal-error', 'Updating of results table failed.'); return; } $isPassed = $total > 0 && $fail === 0 && $error === 0; // Use results_id in the WHERE clause as additional check, just in case // this runresults row is no longer the primary linked one. // This fixes a race condition where (for some reason) 2 clients run the // same run, and the "good" one started last and finishes first. When the // "bad" client finishes and updates this run as not passing, it would // mismatch the results the user would find in the linked report from runresults. // Be sure to use it only as "WHERE" not in "SET", as that could cause // an equally bad effect (unlink a good run). if ($isPassed) { $db->query(str_queryf('UPDATE run_useragent SET completed = completed + 1, status = 2, updated = %s WHERE run_id = %u AND useragent_id = %s AND results_id = %u LIMIT 1;', swarmdb_dateformat(SWARM_NOW), $runID, $browserInfo->getSwarmUaID(), $resultsID)); } else { // If we don't pass and we haven't reached max yet, // set status back to 0 so that this run may be // distributed again (see also GetrunAction), // If we don't pass and did reach the max, set // status=2. $db->query(str_queryf('UPDATE run_useragent SET completed = completed + 1, status = IF(completed + 1 < max, 0, 2), updated = %s WHERE run_id = %u AND useragent_id = %s AND results_id = %u LIMIT 1;', swarmdb_dateformat(SWARM_NOW), $runID, $browserInfo->getSwarmUaID(), $resultsID)); } $this->setData('ok'); }
/** * @param string $sortField * @param string $sortDir * @param string $include * @param string|bool $name */ protected function getOverview($sortField, $sortDir, $include, $name) { $context = $this->getContext(); $db = $context->getDB(); $sortDirQuery = strtoupper($sortDir); $sortFieldQuery = "ORDER BY {$sortField} {$sortDirQuery}"; $whereClause = array(); if ($include === 'active') { $whereClause[] = 'updated >= ' . swarmdb_dateformat(Client::getMaxAge($context)); } if ($name) { $whereClause[] = 'name = \'' . $db->strEncode($name) . '\''; } if (count($whereClause)) { $whereClause = 'WHERE ' . implode(' AND ', $whereClause); } else { $whereClause = ''; } $rows = $db->getRows("SELECT\n\t\t\t\tname,\n\t\t\t\tMAX(updated) as updated\n\t\t\tFROM\n\t\t\t\tclients\n\t\t\t{$whereClause}\n\t\t\tGROUP BY name\n\t\t\t{$sortFieldQuery};"); $results = array(); if ($rows) { foreach ($rows as $row) { $result = array('name' => $row->name, 'viewUrl' => swarmpath("clients/{$row->name}"), 'clientIDs' => array()); $this->addTimestampsTo($result, $row->updated, 'updated'); $results[$row->name] = $result; } } return $results; }