[autotest] Speed up jobs table on View Host tab on AFE.

AFE was using hostname as key to fetch host and related jobs, which is slow
since afe_host_queue_entries and afe_special_tasks tables are using host_id
instead of hostname as foreign key.

I changed to use host id instead, which largely reduced the latency.
Host table's click listeners on View Job and Host List tabs are also changed
to fetch host by id. The arguments of rpc interfaces
get_host_queue_entries_and_special_tasks and
get_num_host_queue_entries_and_special_tasks are also changed to accept host_id
instead of hostname.

BUG=chromium:383589
DEPLOY=afe,apache
TEST=ran afe, ran View Job and Host List tab and cliked host, ran View Host tab

Change-Id: I7e7d1dd195fec211b6eb867976397d1e643b91ce
Reviewed-on: https://chromium-review.googlesource.com/203843
Tested-by: Jiaxi Luo <jiaxiluo@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Jiaxi Luo <jiaxiluo@chromium.org>
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index 4812567..7a376aa 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -810,7 +810,7 @@
 
 # support for host detail view
 
-def get_host_queue_entries_and_special_tasks(hostname, query_start=None,
+def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
                                              query_limit=None):
     """
     @returns an interleaved list of HostQueueEntries and SpecialTasks,
@@ -820,7 +820,7 @@
     total_limit = None
     if query_limit is not None:
         total_limit = query_start + query_limit
-    filter_data = {'host__hostname': hostname,
+    filter_data = {'host': host_id,
                    'query_limit': total_limit,
                    'sort_by': ['-id']}
 
@@ -836,8 +836,8 @@
     return rpc_utils.prepare_for_serialization(interleaved_entries)
 
 
-def get_num_host_queue_entries_and_special_tasks(hostname):
-    filter_data = {'host__hostname': hostname}
+def get_num_host_queue_entries_and_special_tasks(host_id):
+    filter_data = {'host': host_id}
     return (models.HostQueueEntry.query_count(filter_data)
             + models.SpecialTask.query_count(filter_data))
 
diff --git a/frontend/afe/rpc_interface_unittest.py b/frontend/afe/rpc_interface_unittest.py
index 503f555..6ce76eb 100755
--- a/frontend/afe/rpc_interface_unittest.py
+++ b/frontend/afe/rpc_interface_unittest.py
@@ -277,8 +277,9 @@
     def test_get_host_queue_entries_and_special_tasks(self):
         self._setup_special_tasks()
 
+        host = self.hosts[0]
         entries_and_tasks = (
-                rpc_interface.get_host_queue_entries_and_special_tasks('host1'))
+                rpc_interface.get_host_queue_entries_and_special_tasks(host))
 
         paths = [entry['execution_path'] for entry in entries_and_tasks]
         self.assertEquals(paths, ['hosts/host1/3-verify',
@@ -303,7 +304,8 @@
     def test_view_invalid_host(self):
         # RPCs used by View Host page should work for invalid hosts
         self._create_job_helper(hosts=[1])
-        self.hosts[0].delete()
+        host = self.hosts[0]
+        host.delete()
 
         self.assertEquals(1, rpc_interface.get_num_hosts(hostname='host1',
                                                          valid_only=False))
@@ -316,10 +318,10 @@
         self.assertEquals(1, len(data))
 
         count = rpc_interface.get_num_host_queue_entries_and_special_tasks(
-                hostname='host1')
+                host_id=host)
         self.assertEquals(1, count)
         data = rpc_interface.get_host_queue_entries_and_special_tasks(
-                hostname='host1')
+                host_id=host)
         self.assertEquals(1, len(data))
 
 
diff --git a/frontend/client/src/autotest/afe/AfeClient.java b/frontend/client/src/autotest/afe/AfeClient.java
index bea6484..e6fad9f 100644
--- a/frontend/client/src/autotest/afe/AfeClient.java
+++ b/frontend/client/src/autotest/afe/AfeClient.java
@@ -63,8 +63,8 @@
             }
         });
         jobDetail = new JobDetailView(new JobDetailListener() {
-            public void onHostSelected(String hostname) {
-                showHost(hostname);
+            public void onHostSelected(String hostId) {
+                showHost(hostId);
             }
 
             public void onCloneJob(JSONValue cloneInfo) {
@@ -82,15 +82,15 @@
 
         recurringView = new RecurringView(new RecurringSelectListener() {
             public void onRecurringSelected(int jobId) {
-            	showJob(jobId);
+                showJob(jobId);
             }
         });
 
         createJob = AfeUtils.factory.getCreateJobView(jobCreateListener);
 
         hostListView = new HostListView(new HostListListener() {
-            public void onHostSelected(String hostname) {
-                showHost(hostname);
+            public void onHostSelected(String hostId) {
+                showHost(hostId);
             }
         }, jobCreateListener);
 
@@ -125,9 +125,9 @@
         mainTabPanel.selectTabView(jobDetail);
     }
 
-    protected void showHost(String hostname) {
+    protected void showHost(String hostId) {
         hostDetailView.ensureInitialized();
-        hostDetailView.updateObjectId(hostname);
+        hostDetailView.updateObjectId(hostId);
         mainTabPanel.selectTabView(hostDetailView);
     }
 }
diff --git a/frontend/client/src/autotest/afe/HostDetailView.java b/frontend/client/src/autotest/afe/HostDetailView.java
index e5fc3d3..c9babaa 100644
--- a/frontend/client/src/autotest/afe/HostDetailView.java
+++ b/frontend/client/src/autotest/afe/HostDetailView.java
@@ -22,6 +22,9 @@
 
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.json.client.JSONBoolean;
 import com.google.gwt.json.client.JSONObject;
@@ -30,6 +33,7 @@
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.TextBox;
 
 import java.util.List;
 
@@ -70,30 +74,27 @@
                               "get_num_host_queue_entries_and_special_tasks");
 
         private SimpleFilter hostFilter = new SimpleFilter();
-        private String hostname;
+        private String hostId;
 
         public HostJobsTable() {
             super(HOST_JOBS_COLUMNS, normalDataSource);
             addFilter(hostFilter);
         }
 
-        public void setHostname(String hostname) {
-            this.hostname = hostname;
+        public void setHostId(String hostId) {
+            this.hostId = hostId;
             updateFilter();
         }
 
         private void updateFilter() {
-            String key;
             if (getDataSource() == normalDataSource) {
-                key = "host__hostname";
                 sortOnColumn("job__id", SortDirection.DESCENDING);
             } else {
-                key = "hostname";
                 clearSorts();
             }
 
             hostFilter.clear();
-            hostFilter.setParameter(key, new JSONString(hostname));
+            hostFilter.setParameter("host_id", new JSONString(hostId));
         }
 
         public void setSpecialTasksEnabled(boolean enabled) {
@@ -125,6 +126,7 @@
     }
 
     private String hostname = "";
+    private String hostId = "";
     private DataSource hostDataSource = new HostDataSource();
     private HostJobsTable jobsTable = new HostJobsTable();
     private TableDecorator tableDecorator = new TableDecorator(jobsTable);
@@ -139,6 +141,8 @@
     private Button reinstallButton = new Button("Reinstall");
     private Button repairButton = new Button("Repair");
     private CheckBox showSpecialTasks = new CheckBox();
+    private TextBox hostnameInput = new TextBox();
+    private Button hostnameFetchButton = new Button("Go");
 
     public HostDetailView(HostDetailListener hostDetailListener,
                           JobCreateListener jobCreateListener) {
@@ -156,6 +160,10 @@
         return "view_host_fetch_controls";
     }
 
+    private String getFetchByHostnameControlsElementId() {
+        return "view_host_fetch_by_hostname_controls";
+    }
+
     @Override
     protected String getDataElementId() {
         return "view_host_data";
@@ -173,7 +181,7 @@
 
     @Override
     protected String getObjectId() {
-        return hostname;
+        return hostId;
     }
 
     @Override
@@ -181,17 +189,32 @@
         if (id.length() == 0) {
             throw new IllegalArgumentException();
         }
-        this.hostname = id;
+        this.hostId = id;
     }
 
     @Override
     protected void fetchData() {
         JSONObject params = new JSONObject();
+        params.put("id", new JSONString(getObjectId()));
+        fetchDataCommmon(params);
+    }
+
+    private void fetchDataByHostname(String hostname) {
+        JSONObject params = new JSONObject();
         params.put("hostname", new JSONString(hostname));
+        fetchDataCommmon(params);
+    }
+
+    private void fetchDataCommmon(JSONObject params) {
         params.put("valid_only", JSONBoolean.getInstance(false));
         hostDataSource.query(params, this);
     }
 
+    private void fetchByHostname(String hostname) {
+        fetchDataByHostname(hostname);
+        updateHistory();
+    }
+
     @Override
     public void handleTotalResultCount(int totalCount) {}
 
@@ -210,6 +233,8 @@
             return;
         }
 
+        setObjectId(currentHostObject.get("id").toString());
+
         String lockedText = Utils.jsonToString(currentHostObject.get(HostDataSource.LOCKED_TEXT));
         if (currentHostObject.get("locked").isBoolean().booleanValue()) {
             String lockedBy = Utils.jsonToString(currentHostObject.get("locked_by"));
@@ -223,11 +248,13 @@
         showField(currentHostObject, HostDataSource.OTHER_LABELS, "view_host_labels");
         showText(lockedText, "view_host_locked");
         showField(currentHostObject, "protection", "view_host_protection");
+        hostname = currentHostObject.get("hostname").isString().stringValue();
         String pageTitle = "Host " + hostname;
+        hostnameInput.setText(hostname);
         updateLockButton();
         displayObjectData(pageTitle);
 
-        jobsTable.setHostname(hostname);
+        jobsTable.setHostId(getObjectId());
         jobsTable.refresh();
     }
 
@@ -235,6 +262,22 @@
     public void initialize() {
         super.initialize();
 
+        // Replace fetch by id with fetch by hostname
+        addWidget(hostnameInput, getFetchByHostnameControlsElementId());
+        addWidget(hostnameFetchButton, getFetchByHostnameControlsElementId());
+
+        hostnameInput.addKeyPressHandler(new KeyPressHandler() {
+            public void onKeyPress (KeyPressEvent event) {
+                if (event.getCharCode() == (char) KeyCodes.KEY_ENTER)
+                    fetchByHostname(hostnameInput.getText());
+            }
+        });
+        hostnameFetchButton.addClickHandler(new ClickHandler() {
+            public void onClick(ClickEvent event) {
+                fetchByHostname(hostnameInput.getText());
+            }
+        });
+
         jobsTable.setRowsPerPage(JOBS_PER_PAGE);
         jobsTable.setClickable(true);
         jobsTable.addListener(new DynamicTableListener() {
diff --git a/frontend/client/src/autotest/afe/HostListView.java b/frontend/client/src/autotest/afe/HostListView.java
index 55d1610..7abf9fd 100644
--- a/frontend/client/src/autotest/afe/HostListView.java
+++ b/frontend/client/src/autotest/afe/HostListView.java
@@ -19,7 +19,7 @@
     protected static final int HOSTS_PER_PAGE = 30;
 
     public interface HostListListener {
-        public void onHostSelected(String hostname);
+        public void onHostSelected(String id);
     }
 
     protected HostListListener hostListListener = null;
@@ -53,8 +53,8 @@
         table.setClickable(true);
         table.addListener(new DynamicTableListener() {
             public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
-                String hostname = row.get("hostname").isString().stringValue();
-                hostListListener.onHostSelected(hostname);
+                String hostId = row.get("id").toString();
+                hostListListener.onHostSelected(hostId);
             }
 
             public void onTableRefreshed() {}
diff --git a/frontend/client/src/autotest/afe/JobDetailView.java b/frontend/client/src/autotest/afe/JobDetailView.java
index 82faf86..7100822 100644
--- a/frontend/client/src/autotest/afe/JobDetailView.java
+++ b/frontend/client/src/autotest/afe/JobDetailView.java
@@ -52,7 +52,7 @@
     public static final String RESULTS_MAX_HEIGHT = "500px";
 
     public interface JobDetailListener {
-        public void onHostSelected(String hostname);
+        public void onHostSelected(String hostId);
         public void onCloneJob(JSONValue result);
         public void onCreateRecurringJob(int id);
     }
@@ -207,8 +207,8 @@
         hostsTable.addListener(new DynamicTableListener() {
             public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
                 JSONObject host = row.get("host").isObject();
-                String hostname = host.get("hostname").isString().stringValue();
-                listener.onHostSelected(hostname);
+                String id = host.get("id").toString();
+                listener.onHostSelected(id);
             }
 
             public void onTableRefreshed() {}
diff --git a/frontend/client/src/autotest/public/AfeClient.html b/frontend/client/src/autotest/public/AfeClient.html
index a93d5e1..f74125c 100644
--- a/frontend/client/src/autotest/public/AfeClient.html
+++ b/frontend/client/src/autotest/public/AfeClient.html
@@ -181,8 +181,9 @@
       </div>
 
       <div id="view_host" title="View Host">
-        <span id="view_host_fetch" class="box-full">Fetch host:
-          <span id="view_host_fetch_controls"></span>
+        <span id="view_host_fetch" class="box-full">Fetch host by hostname:
+          <span id="view_host_fetch_controls" class="hidden"></span>
+          <span id="view_host_fetch_by_hostname_controls"></span>
         </span><br><br>
         <div id="view_host_title" class="title"></div>
         <div>