<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[177614] trunk/Websites/perf.webkit.org</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/177614">177614</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2014-12-19 18:38:04 -0800 (Fri, 19 Dec 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Perf dashboard should support authentication via a slave password
https://bugs.webkit.org/show_bug.cgi?id=139837

Reviewed by Andreas Kling.

For historical reasons, perf dashboard conflated builders and build slaves. As a result we ended up
having to add multiple builders with the same password when a single build slave is shared among them.

This patch introduces the concept of build_slave into the perf dashboard to end this madness.

* init-database.sql: Added build_slave table as well as references to it in builds and reports.

* public/admin/build-slaves.php: Added.

* public/admin/builders.php: Added the support for updating passwords.

* public/include/admin-header.php:
(update_field): Takes an extra argument when a new value needs to be supplied by the caller instead of
being retrieved from $_POST.
(AdministrativePage::render_table): Use array_get to retrieve a value out of the database row since
the raw may not exist (e.g. new_password).
(AdministrativePage::render_form_to_add): Added the support for post_insertion. Don't render the form
control here when this flag evaluates to TRUE.

* public/include/report-processor.php:
(ReportProcessor::process): Added the logic to authenticate with slaveName and slavePassword if those
values are present in the report. In addition, try authenticating builderName with slavePassword if
builderPassword is not specified. When neither password is specified, exit with BuilderNotFound.
Also insert the slave or the builder whichever is missing after we've successfully authenticated.
(ReportProcessor::construct_build_data): Takes a builder ID and an optional slave ID instead of
a builder row.
(ReportProcessor::store_report): Store the slave ID with the report.
(ReportProcessor::resolve_build_id): Exit with MismatchingBuildSlave when the slave associated with
the matching build is different from what's being reported.

* tests/api-report.js: Added a bunch of tests to test the new features of /api/report.
(.addSlave): Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorginitdatabasesql">trunk/Websites/perf.webkit.org/init-database.sql</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicadminbuildersphp">trunk/Websites/perf.webkit.org/public/admin/builders.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludeadminheaderphp">trunk/Websites/perf.webkit.org/public/include/admin-header.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludereportprocessorphp">trunk/Websites/perf.webkit.org/public/include/report-processor.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtestsapireportjs">trunk/Websites/perf.webkit.org/tests/api-report.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgpublicadminbuildslavesphp">trunk/Websites/perf.webkit.org/public/admin/build-slaves.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -1,3 +1,43 @@
</span><ins>+2014-12-19  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Perf dashboard should support authentication via a slave password
+        https://bugs.webkit.org/show_bug.cgi?id=139837
+
+        Reviewed by Andreas Kling.
+
+        For historical reasons, perf dashboard conflated builders and build slaves. As a result we ended up
+        having to add multiple builders with the same password when a single build slave is shared among them.
+
+        This patch introduces the concept of build_slave into the perf dashboard to end this madness.
+
+        * init-database.sql: Added build_slave table as well as references to it in builds and reports.
+
+        * public/admin/build-slaves.php: Added.
+
+        * public/admin/builders.php: Added the support for updating passwords.
+
+        * public/include/admin-header.php:
+        (update_field): Takes an extra argument when a new value needs to be supplied by the caller instead of
+        being retrieved from $_POST.
+        (AdministrativePage::render_table): Use array_get to retrieve a value out of the database row since
+        the raw may not exist (e.g. new_password).
+        (AdministrativePage::render_form_to_add): Added the support for post_insertion. Don't render the form
+        control here when this flag evaluates to TRUE.
+
+        * public/include/report-processor.php:
+        (ReportProcessor::process): Added the logic to authenticate with slaveName and slavePassword if those
+        values are present in the report. In addition, try authenticating builderName with slavePassword if
+        builderPassword is not specified. When neither password is specified, exit with BuilderNotFound.
+        Also insert the slave or the builder whichever is missing after we've successfully authenticated.
+        (ReportProcessor::construct_build_data): Takes a builder ID and an optional slave ID instead of
+        a builder row.
+        (ReportProcessor::store_report): Store the slave ID with the report.
+        (ReportProcessor::resolve_build_id): Exit with MismatchingBuildSlave when the slave associated with
+        the matching build is different from what's being reported.
+
+        * tests/api-report.js: Added a bunch of tests to test the new features of /api/report.
+        (.addSlave): Added.
+
</ins><span class="cx"> 2014-12-18  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         New perf dashboard should not duplicate author information in each commit
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorginitdatabasesql"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/init-database.sql (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/init-database.sql        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/init-database.sql        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -7,6 +7,7 @@
</span><span class="cx"> DROP TABLE committers CASCADE;
</span><span class="cx"> DROP TABLE commits CASCADE;
</span><span class="cx"> DROP TABLE build_commits CASCADE;
</span><ins>+DROP TABLE build_slaves CASCADE;
</ins><span class="cx"> DROP TABLE builders CASCADE;
</span><span class="cx"> DROP TABLE repositories CASCADE;
</span><span class="cx"> DROP TABLE platforms CASCADE;
</span><span class="lines">@@ -46,12 +47,18 @@
</span><span class="cx"> CREATE TABLE builders (
</span><span class="cx">     builder_id serial PRIMARY KEY,
</span><span class="cx">     builder_name varchar(64) NOT NULL UNIQUE,
</span><del>-    builder_password_hash character(64) NOT NULL,
</del><ins>+    builder_password_hash character(64),
</ins><span class="cx">     builder_build_url varchar(1024));
</span><span class="cx"> 
</span><ins>+CREATE TABLE build_slaves (
+    slave_id serial PRIMARY KEY,
+    slave_name varchar(64) NOT NULL UNIQUE,
+    slave_password_hash character(64));
+
</ins><span class="cx"> CREATE TABLE builds (
</span><span class="cx">     build_id serial PRIMARY KEY,
</span><span class="cx">     build_builder integer REFERENCES builders ON DELETE CASCADE,
</span><ins>+    build_slave integer REFERENCES build_slaves ON DELETE CASCADE,
</ins><span class="cx">     build_number integer NOT NULL,
</span><span class="cx">     build_time timestamp NOT NULL,
</span><span class="cx">     build_latest_revision timestamp,
</span><span class="lines">@@ -135,6 +142,7 @@
</span><span class="cx"> CREATE TABLE reports (
</span><span class="cx">     report_id serial PRIMARY KEY,
</span><span class="cx">     report_builder integer NOT NULL REFERENCES builders ON DELETE RESTRICT,
</span><ins>+    report_slave integer REFERENCES build_slaves ON DELETE RESTRICT,
</ins><span class="cx">     report_build_number integer,
</span><span class="cx">     report_build integer REFERENCES builds,
</span><span class="cx">     report_created_at timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicadminbuildslavesphp"></a>
<div class="addfile"><h4>Added: trunk/Websites/perf.webkit.org/public/admin/build-slaves.php (0 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/admin/build-slaves.php                                (rev 0)
+++ trunk/Websites/perf.webkit.org/public/admin/build-slaves.php        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -0,0 +1,35 @@
</span><ins>+&lt;?php
+
+require('../include/admin-header.php');
+
+if ($db) {
+
+    if ($action == 'add') {
+        if ($db-&gt;insert_row('build_slaves', 'slave', array('name' =&gt; $_POST['name'], 'password_hash' =&gt; hash('sha256', $_POST['password'])))) {
+            notice('Inserted the new slave.');
+            regenerate_manifest();
+        } else
+            notice('Could not add the slave.');
+    } else if ($action == 'update') {
+        if (update_field('build_slaves', 'slave', 'name'))
+            regenerate_manifest();
+        else if (update_field('build_slaves', 'slave', 'password_hash', hash('sha256', $_POST['new_password'])))
+            regenerate_manifest();
+        else
+            notice('Invalid parameters.');
+    }
+
+    $page = new AdministrativePage($db, 'build_slaves', 'slave', array(
+        'name' =&gt; array('size' =&gt; 50, 'editing_mode' =&gt; 'string'),
+        'password_hash' =&gt; array(),
+        'password' =&gt; array('pre_insertion' =&gt; TRUE, 'editing_mode' =&gt; 'string'),
+        'new_password' =&gt; array('post_insertion' =&gt; TRUE, 'editing_mode' =&gt; 'string'),
+    ));
+
+    $page-&gt;render_table('name');
+    $page-&gt;render_form_to_add();
+}
+
+require('../include/admin-footer.php');
+
+?&gt;
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicadminbuildersphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/admin/builders.php (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/admin/builders.php        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/public/admin/builders.php        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -14,6 +14,8 @@
</span><span class="cx">     } else if ($action == 'update') {
</span><span class="cx">         if (update_field('builders', 'builder', 'name') || update_field('builders', 'builder', 'build_url'))
</span><span class="cx">             regenerate_manifest();
</span><ins>+        else if (update_field('build_slaves', 'slave', 'password_hash', hash('sha256', $_POST['new_password'])))
+            regenerate_manifest();
</ins><span class="cx">         else
</span><span class="cx">             notice('Invalid parameters.');
</span><span class="cx">     }
</span><span class="lines">@@ -22,6 +24,7 @@
</span><span class="cx">         'name' =&gt; array('size' =&gt; 50, 'editing_mode' =&gt; 'string'),
</span><span class="cx">         'password_hash' =&gt; array(),
</span><span class="cx">         'password' =&gt; array('pre_insertion' =&gt; TRUE, 'editing_mode' =&gt; 'string'),
</span><ins>+        'new_password' =&gt; array('post_insertion' =&gt; TRUE, 'editing_mode' =&gt; 'string'),
</ins><span class="cx">         'build_url' =&gt; array('label' =&gt; 'Build URL', 'size' =&gt; 100, 'editing_mode' =&gt; 'url'),
</span><span class="cx">     ));
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludeadminheaderphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/admin-header.php (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/admin-header.php        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/public/include/admin-header.php        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -18,6 +18,7 @@
</span><span class="cx">     &lt;li&gt;&lt;a href=&quot;/admin/tests&quot;&gt;Tests&lt;/a&gt;&lt;/li&gt;
</span><span class="cx">     &lt;li&gt;&lt;a href=&quot;/admin/aggregators&quot;&gt;Aggregators&lt;/a&gt;&lt;/li&gt;
</span><span class="cx">     &lt;li&gt;&lt;a href=&quot;/admin/builders&quot;&gt;Builders&lt;/a&gt;&lt;/li&gt;
</span><ins>+    &lt;li&gt;&lt;a href=&quot;/admin/build-slaves&quot;&gt;Slaves&lt;/a&gt;&lt;/li&gt;
</ins><span class="cx">     &lt;li&gt;&lt;a href=&quot;/admin/repositories&quot;&gt;Repositories&lt;/a&gt;&lt;/li&gt;
</span><span class="cx">     &lt;li&gt;&lt;a href=&quot;/admin/bug-trackers&quot;&gt;Bug Trackers&lt;/a&gt;&lt;/li&gt;
</span><span class="cx"> &lt;/ul&gt;
</span><span class="lines">@@ -56,18 +57,24 @@
</span><span class="cx">     return false;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-function update_field($table, $prefix, $field_name) {
</del><ins>+function update_field($table, $prefix, $field_name, $new_value = NULL) {
</ins><span class="cx">     global $db;
</span><span class="cx"> 
</span><del>-    if (!array_key_exists('id', $_POST) || (array_get($_POST, 'updated-column') != $field_name &amp;&amp; !array_key_exists($field_name, $_POST)))
</del><ins>+    if (!array_key_exists('id', $_POST))
</ins><span class="cx">         return FALSE;
</span><span class="cx"> 
</span><span class="cx">     $id = intval($_POST['id']);
</span><span class="cx">     $prefixed_field_name = $prefix . '_' . $field_name;
</span><span class="cx">     $id_field_name = $prefix . '_id';
</span><span class="cx"> 
</span><ins>+    if ($new_value == NULL) {
+        if (array_get($_POST, 'updated-column') != $field_name &amp;&amp; !array_key_exists($field_name, $_POST))
+            return FALSE;
+        $new_value = array_get($_POST, $field_name);
+    }
+
</ins><span class="cx">     execute_query_and_expect_one_row_to_be_affected(&quot;UPDATE $table SET $prefixed_field_name = \$2 WHERE $id_field_name = \$1&quot;,
</span><del>-        array($id, array_get($_POST, $field_name)),
</del><ins>+        array($id, $new_value),
</ins><span class="cx">         &quot;Updated the $prefix $id&quot;,
</span><span class="cx">         &quot;Could not update $prefix $id&quot;);
</span><span class="cx"> 
</span><span class="lines">@@ -226,7 +233,7 @@
</span><span class="cx">                         continue;
</span><span class="cx">                     }
</span><span class="cx"> 
</span><del>-                    $value = htmlspecialchars($row[$this-&gt;prefix . '_' . $name], ENT_QUOTES);
</del><ins>+                    $value = htmlspecialchars(array_get($row, $this-&gt;prefix . '_' . $name), ENT_QUOTES);
</ins><span class="cx">                     $editing_mode = array_get($this-&gt;column_info[$name], 'editing_mode');
</span><span class="cx">                     if (!$editing_mode) {
</span><span class="cx">                         echo &quot;&lt;td$rowspan_if_needed&gt;$value&lt;/td&gt;\n&quot;;
</span><span class="lines">@@ -295,6 +302,8 @@
</span><span class="cx">             $editing_mode = array_get($this-&gt;column_info[$name], 'editing_mode');
</span><span class="cx">             if (array_get($this-&gt;column_info[$name], 'custom') || !$editing_mode)
</span><span class="cx">                 continue;
</span><ins>+            if (array_get($this-&gt;column_info[$name], 'post_insertion'))
+                continue;
</ins><span class="cx"> 
</span><span class="cx">             $label = htmlspecialchars($this-&gt;column_label($name));
</span><span class="cx">             echo &quot;&lt;label&gt;$label&lt;br&gt;\n&quot;;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludereportprocessorphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/report-processor.php (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/report-processor.php        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/public/include/report-processor.php        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -39,16 +39,42 @@
</span><span class="cx">         array_key_exists('buildTime', $report) or $this-&gt;exit_with_error('MissingBuildTime');
</span><span class="cx"> 
</span><span class="cx">         $builder_info = array('name' =&gt; $report['builderName']);
</span><del>-        if (!$existing_report_id)
-            $builder_info['password_hash'] = hash('sha256', $report['builderPassword']);
</del><ins>+        $slave_name = array_get($report, 'slaveName', NULL);
+        $slave_id = NULL;
+        if (!$existing_report_id) {
+            $hash = NULL;
+            if ($slave_name &amp;&amp; array_key_exists('slavePassword', $report)) {
+                $hash = hash('sha256', $report['slavePassword']);
+                $slave = $this-&gt;db-&gt;select_first_row('build_slaves', 'slave', array('name' =&gt; $slave_name, 'password_hash' =&gt; $hash));
+                if ($slave)
+                    $slave_id = $slave['slave_id'];
+            } else if (array_key_exists('builderPassword', $report))
+                $hash = hash('sha256', $report['builderPassword']);
+
+            if (!$hash)
+                $this-&gt;exit_with_error('BuilderNotFound');
+            if (!$slave_id)
+                $builder_info['password_hash'] = $hash;
+        }
+
</ins><span class="cx">         if (array_key_exists('builderPassword', $report))
</span><span class="cx">             unset($report['builderPassword']);
</span><ins>+        if (array_key_exists('slavePassword', $report))
+            unset($report['slavePassword']);
</ins><span class="cx"> 
</span><del>-        $matched_builder = $this-&gt;db-&gt;select_first_row('builders', 'builder', $builder_info);
-        if (!$matched_builder)
-            $this-&gt;exit_with_error('BuilderNotFound', array('name' =&gt; $builder_info['name']));
</del><ins>+        $builder_id = NULL;
+        if ($slave_id)
+            $builder_id = $this-&gt;db-&gt;select_or_insert_row('builders', 'builder', $builder_info);
+        else {
+            $builder = $this-&gt;db-&gt;select_first_row('builders', 'builder', $builder_info);
+            if (!$builder)
+                $this-&gt;exit_with_error('BuilderNotFound', array('name' =&gt; $builder_info['name']));
+            $builder_id = $builder['builder_id'];
+            if ($slave_name)
+                $slave_id = $this-&gt;db-&gt;select_or_insert_row('build_slaves', 'slave', array('name' =&gt; $slave_name));
+        }
</ins><span class="cx"> 
</span><del>-        $build_data = $this-&gt;construct_build_data($report, $matched_builder);
</del><ins>+        $build_data = $this-&gt;construct_build_data($report, $builder_id, $slave_id);
</ins><span class="cx">         if (!$existing_report_id)
</span><span class="cx">             $this-&gt;store_report($report, $build_data);
</span><span class="cx"> 
</span><span class="lines">@@ -67,16 +93,19 @@
</span><span class="cx">         $this-&gt;runs-&gt;commit($platform_id, $build_id);
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    private function construct_build_data($report, $builder) {
</del><ins>+    private function construct_build_data($report, $builder_id, $slave_id) {
</ins><span class="cx">         array_key_exists('buildNumber', $report) or $this-&gt;exit_with_error('MissingBuildNumber');
</span><span class="cx">         array_key_exists('buildTime', $report) or $this-&gt;exit_with_error('MissingBuildTime');
</span><span class="cx"> 
</span><del>-        return array('builder' =&gt; $builder['builder_id'], 'number' =&gt; $report['buildNumber'], 'time' =&gt; $report['buildTime']);
</del><ins>+        return array('builder' =&gt; $builder_id, 'slave' =&gt; $slave_id, 'number' =&gt; $report['buildNumber'], 'time' =&gt; $report['buildTime']);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     private function store_report($report, $build_data) {
</span><span class="cx">         assert(!$this-&gt;report_id);
</span><del>-        $this-&gt;report_id = $this-&gt;db-&gt;insert_row('reports', 'report', array('builder' =&gt; $build_data['builder'], 'build_number' =&gt; $build_data['number'],
</del><ins>+        $this-&gt;report_id = $this-&gt;db-&gt;insert_row('reports', 'report', array(
+            'builder' =&gt; $build_data['builder'],
+            'slave' =&gt; $build_data['slave'],
+            'build_number' =&gt; $build_data['number'],
</ins><span class="cx">             'content' =&gt; json_encode($report)));
</span><span class="cx">         if (!$this-&gt;report_id)
</span><span class="cx">             $this-&gt;exit_with_error('FailedToStoreRunReport');
</span><span class="lines">@@ -84,11 +113,15 @@
</span><span class="cx"> 
</span><span class="cx">     private function resolve_build_id($build_data, $revisions) {
</span><span class="cx">         // FIXME: This code has a race condition. See &lt;rdar://problem/15876303&gt;.
</span><del>-        $results = $this-&gt;db-&gt;query_and_fetch_all(&quot;SELECT build_id FROM builds WHERE build_builder = $1 AND build_number = $2 AND build_time &lt;= $3 AND build_time + interval '1 day' &gt; $3&quot;,
</del><ins>+        $results = $this-&gt;db-&gt;query_and_fetch_all(&quot;SELECT build_id, build_slave FROM builds
+            WHERE build_builder = $1 AND build_number = $2 AND build_time &lt;= $3 AND build_time + interval '1 day' &gt; $3&quot;,
</ins><span class="cx">             array($build_data['builder'], $build_data['number'], $build_data['time']));
</span><del>-        if ($results)
-            $build_id = $results[0]['build_id'];
-        else
</del><ins>+        if ($results) {
+            $first_result = $results[0];
+            if ($first_result['build_slave'] != $build_data['slave'])
+                $this-&gt;exit_with_error('MismatchingBuildSlave', array('storedBuild' =&gt; $results, 'reportedBuildData' =&gt; $build_data));
+            $build_id = $first_result['build_id'];
+        } else
</ins><span class="cx">             $build_id = $this-&gt;db-&gt;insert_row('builds', 'build', $build_data);
</span><span class="cx">         if (!$build_id)
</span><span class="cx">             $this-&gt;exit_with_error('FailedToInsertBuild', $build_data);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtestsapireportjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tests/api-report.js (177613 => 177614)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tests/api-report.js        2014-12-20 01:21:38 UTC (rev 177613)
+++ trunk/Websites/perf.webkit.org/tests/api-report.js        2014-12-20 02:38:04 UTC (rev 177614)
</span><span class="lines">@@ -3,6 +3,7 @@
</span><span class="cx">         &quot;buildNumber&quot;: &quot;123&quot;,
</span><span class="cx">         &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
</span><span class="cx">         &quot;builderName&quot;: &quot;someBuilder&quot;,
</span><ins>+        &quot;slaveName&quot;: &quot;someSlave&quot;,
</ins><span class="cx">         &quot;builderPassword&quot;: &quot;somePassword&quot;,
</span><span class="cx">         &quot;platform&quot;: &quot;Mountain Lion&quot;,
</span><span class="cx">         &quot;tests&quot;: {},
</span><span class="lines">@@ -16,11 +17,35 @@
</span><span class="cx">             }
</span><span class="cx">         }}];
</span><span class="cx"> 
</span><ins>+    var emptySlaveReport = [{
+        &quot;buildNumber&quot;: &quot;123&quot;,
+        &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+        &quot;builderName&quot;: &quot;someBuilder&quot;,
+        &quot;builderPassword&quot;: &quot;somePassword&quot;,
+        &quot;slaveName&quot;: &quot;someSlave&quot;,
+        &quot;slavePassword&quot;: &quot;otherPassword&quot;,
+        &quot;platform&quot;: &quot;Mountain Lion&quot;,
+        &quot;tests&quot;: {},
+        &quot;revisions&quot;: {
+            &quot;OS X&quot;: {
+                &quot;revision&quot;: &quot;10.8.2 12C60&quot;
+            },
+            &quot;WebKit&quot;: {
+                &quot;revision&quot;: &quot;141977&quot;,
+                &quot;timestamp&quot;: &quot;2013-02-06T08:55:20.9Z&quot;
+            }
+        }}];
+
</ins><span class="cx">     function addBuilder(report, callback) {
</span><span class="cx">         queryAndFetchAll('INSERT INTO builders (builder_name, builder_password_hash) values ($1, $2)',
</span><span class="cx">             [report[0].builderName, sha256(report[0].builderPassword)], callback);
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    function addSlave(report, callback) {
+        queryAndFetchAll('INSERT INTO build_slaves (slave_name, slave_password_hash) values ($1, $2)',
+            [report[0].slaveName, sha256(report[0].slavePassword)], callback);
+    }
+
</ins><span class="cx">     it(&quot;should reject error when builder name is missing&quot;, function () {
</span><span class="cx">         postJSON('/api/report/', [{&quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;}], function (response) {
</span><span class="cx">             assert.equal(response.statusCode, 200);
</span><span class="lines">@@ -53,6 +78,28 @@
</span><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    it(&quot;should reject a report without a builder password&quot;, function () {
+        addBuilder(emptyReport, function () {
+            var report = [{
+                &quot;buildNumber&quot;: &quot;123&quot;,
+                &quot;buildTime&quot;: &quot;2013-02-28T10:12:03.388304&quot;,
+                &quot;builderName&quot;: &quot;someBuilder&quot;,
+                &quot;tests&quot;: {},
+                &quot;revisions&quot;: {}}];
+            postJSON('/api/report/', report, function (response) {
+                assert.equal(response.statusCode, 200);
+                assert.notEqual(JSON.parse(response.responseText)['status'], 'OK');
+                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
+                assert.equal(JSON.parse(response.responseText)['processedRuns'], 0);
+
+                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
+                    assert.equal(rows[0].count, 0);
+                    notifyDone();
+                });
+            });
+        });
+    });
+
</ins><span class="cx">     it(&quot;should store a report from a valid builder&quot;, function () {
</span><span class="cx">         addBuilder(emptyReport, function () {
</span><span class="cx">             postJSON('/api/report/', emptyReport, function (response) {
</span><span class="lines">@@ -68,6 +115,41 @@
</span><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    it(&quot;should treat the slave password as the builder password if there is no matching slave&quot;, function () {
+        addBuilder(emptyReport, function () {
+            emptyReport[0]['slavePassword'] = emptyReport[0]['builderPassword'];
+            delete emptyReport[0]['builderPassword'];
+            postJSON('/api/report/', emptyReport, function (response) {
+                emptyReport[0]['builderPassword'] = emptyReport[0]['slavePassword'];
+                delete emptyReport[0]['slavePassword'];
+
+                assert.equal(response.statusCode, 200);
+                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
+                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
+                assert.equal(JSON.parse(response.responseText)['processedRuns'], 1);
+                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
+                    assert.equal(rows[0].count, 1);
+                    notifyDone();
+                });
+            });
+        });
+    });
+
+    it(&quot;should store a report from a valid slave&quot;, function () {
+        addSlave(emptySlaveReport, function () {
+            postJSON('/api/report/', emptySlaveReport, function (response) {
+                assert.equal(response.statusCode, 200);
+                assert.equal(JSON.parse(response.responseText)['status'], 'OK');
+                assert.equal(JSON.parse(response.responseText)['failureStored'], false);
+                assert.equal(JSON.parse(response.responseText)['processedRuns'], 1);
+                queryAndFetchAll('SELECT COUNT(*) from reports', [], function (rows) {
+                    assert.equal(rows[0].count, 1);
+                    notifyDone();
+                });
+            });
+        });
+    });
+
</ins><span class="cx">     it(&quot;should store the builder name but not the builder password&quot;, function () {
</span><span class="cx">         addBuilder(emptyReport, function () {
</span><span class="cx">             postJSON('/api/report/', emptyReport, function (response) {
</span><span class="lines">@@ -81,6 +163,28 @@
</span><span class="cx">         });
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    it(&quot;should add a slave if there isn't one and the report was authenticated by a builder&quot;, function () {
+        addBuilder(emptyReport, function () {
+            postJSON('/api/report/', emptyReport, function (response) {
+                queryAndFetchAll('SELECT * from build_slaves', [], function (rows) {
+                    assert.strictEqual(rows[0].slave_name, emptyReport[0].slaveName);
+                    notifyDone();
+                });
+            });
+        });
+    });
+
+    it(&quot;should add a builder if there isn't one and the report was authenticated by a slave&quot;, function () {
+        addSlave(emptySlaveReport, function () {
+            postJSON('/api/report/', emptySlaveReport, function (response) {
+                queryAndFetchAll('SELECT * from builders', [], function (rows) {
+                    assert.strictEqual(rows[0].builder_name, emptyReport[0].builderName);
+                    notifyDone();
+                });
+            });
+        });
+    });
+
</ins><span class="cx">     it(&quot;should add a build&quot;, function () {
</span><span class="cx">         addBuilder(emptyReport, function () {
</span><span class="cx">             postJSON('/api/report/', emptyReport, function (response) {
</span></span></pre>
</div>
</div>

</body>
</html>