[autotest] Support uploading a service key file to moblab

Add RPC to accept an uploaded key file and save it to
/home/moblab/.launch_control_key
Add a new tab in moblab setup page to allow user to upload a service key file.

BUG=chromium:570705
TEST=verify with a local moblab build

Change-Id: Ib54a6b707cb93bb5220e29ed6feeb854f5f4abe8
Reviewed-on: https://chromium-review.googlesource.com/321140
Commit-Ready: Dan Shi <dshi@google.com>
Tested-by: Dan Shi <dshi@google.com>
Reviewed-by: Simran Basi <sbasi@chromium.org>
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index a7cb19a..e0a7e0a 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -325,6 +325,22 @@
     shutil.copyfile(boto_key, moblab_host.MOBLAB_BOTO_LOCATION)
 
 
+@moblab_only
+def set_launch_control_key(launch_control_key):
+    """Update the launch_control_key file.
+
+    @param launch_control_key: File name of launch_control_key uploaded through
+            handle_file_upload.
+    """
+    if not os.path.exists(launch_control_key):
+        raise error.RPCException('Launch Control key: %s does not exist!' %
+                                 launch_control_key)
+    shutil.copyfile(launch_control_key,
+                    moblab_host.MOBLAB_LAUNCH_CONTROL_KEY_LOCATION)
+    # Restart the devserver service.
+    os.system('sudo restart moblab-devserver-init')
+
+
 def get_job_history(**filter_data):
     """Get history of the job, including the special tasks executed for the job
 
diff --git a/frontend/afe/site_rpc_interface_unittest.py b/frontend/afe/site_rpc_interface_unittest.py
index df87752..0606dbe 100755
--- a/frontend/afe/site_rpc_interface_unittest.py
+++ b/frontend/afe/site_rpc_interface_unittest.py
@@ -27,6 +27,7 @@
 from autotest_lib.server import utils
 from autotest_lib.server.cros.dynamic_suite import control_file_getter
 from autotest_lib.server.cros.dynamic_suite import constants
+from autotest_lib.server.hosts import moblab_host
 
 
 CLIENT = control_data.CONTROL_TYPE_NAMES.CLIENT
@@ -447,6 +448,24 @@
         site_rpc_interface.set_boto_key(boto_key)
 
 
+    def testSetLaunchControlKey(self):
+        """Ensure that the Launch Control key path supplied is copied correctly.
+        """
+        self.setIsMoblab(True)
+        launch_control_key = '/tmp/launch_control'
+        site_rpc_interface.os = self.mox.CreateMockAnything()
+        site_rpc_interface.os.path = self.mox.CreateMockAnything()
+        site_rpc_interface.os.path.exists(launch_control_key).AndReturn(
+                True)
+        site_rpc_interface.shutil = self.mox.CreateMockAnything()
+        site_rpc_interface.shutil.copyfile(
+                launch_control_key,
+                moblab_host.MOBLAB_LAUNCH_CONTROL_KEY_LOCATION)
+        site_rpc_interface.os.system('sudo restart moblab-devserver-init')
+        self.mox.ReplayAll()
+        site_rpc_interface.set_launch_control_key(launch_control_key)
+
+
     def _get_records_for_sending_to_master(self):
         return [{'control_file': 'foo',
                  'control_type': 1,
diff --git a/frontend/client/src/autotest/moblab/BotoKeyView.java b/frontend/client/src/autotest/moblab/BotoKeyView.java
index d8f6c48..03393f7 100644
--- a/frontend/client/src/autotest/moblab/BotoKeyView.java
+++ b/frontend/client/src/autotest/moblab/BotoKeyView.java
@@ -1,29 +1,6 @@
 package autotest.moblab;
 
-import autotest.common.JsonRpcCallback;
-import autotest.common.JsonRpcProxy;
-import autotest.common.SimpleCallback;
-import autotest.common.ui.TabView;
-import autotest.common.ui.NotifyManager;
-
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.json.client.JSONObject;
-import com.google.gwt.json.client.JSONString;
-import com.google.gwt.json.client.JSONValue;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.FileUpload;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
-import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
-import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
-import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
-
-
-public class BotoKeyView extends TabView {
-    private FileUpload botoKeyUpload;
-    private Button submitButton;
-    private FormPanel botoKeyUploadForm;
+public class BotoKeyView extends KeyUploadView {
 
     @Override
     public String getElementId() {
@@ -32,44 +9,13 @@
 
     @Override
     public void initialize() {
+        fileUploadName = "botokey";
+        uploadViewName = "view_boto_key";
+        submitButtonName = "view_submit_boto_key";
+        rpcName = "set_boto_key";
+        rpcArgName = "boto_key";
+        successMessage = "Boto Key uploaded.";
+
         super.initialize();
-
-        botoKeyUpload = new FileUpload();
-        botoKeyUpload.setName("botokey");
-
-        botoKeyUploadForm = new FormPanel();
-        botoKeyUploadForm.setAction(JsonRpcProxy.AFE_BASE_URL + "upload/");
-        botoKeyUploadForm.setEncoding(FormPanel.ENCODING_MULTIPART);
-        botoKeyUploadForm.setMethod(FormPanel.METHOD_POST);
-        botoKeyUploadForm.setWidget(botoKeyUpload);
-
-        submitButton = new Button("Submit", new ClickHandler() {
-            public void onClick(ClickEvent event) {
-                botoKeyUploadForm.submit();
-            }
-        });
-
-        botoKeyUploadForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {
-            public void onSubmitComplete(SubmitCompleteEvent event) {
-                String fileName = event.getResults();
-                JSONObject params = new JSONObject();
-                params.put("boto_key", new JSONString(fileName));
-                rpcCall(params);
-            }
-        });
-
-        addWidget(botoKeyUploadForm, "view_boto_key");
-        addWidget(submitButton, "view_submit_boto_key");
     }
-
-    public void rpcCall(JSONObject params) {
-        JsonRpcProxy rpcProxy = JsonRpcProxy.getProxy();
-        rpcProxy.rpcCall("set_boto_key", params, new JsonRpcCallback() {
-            @Override
-            public void onSuccess(JSONValue result) {
-                NotifyManager.getInstance().showMessage("Boto key uploaded.");
-            }
-        });
-    }
-
-}
\ No newline at end of file
+}
diff --git a/frontend/client/src/autotest/moblab/KeyUploadView.java b/frontend/client/src/autotest/moblab/KeyUploadView.java
new file mode 100644
index 0000000..953144a
--- /dev/null
+++ b/frontend/client/src/autotest/moblab/KeyUploadView.java
@@ -0,0 +1,81 @@
+package autotest.moblab;
+
+import autotest.common.JsonRpcCallback;
+import autotest.common.JsonRpcProxy;
+import autotest.common.SimpleCallback;
+import autotest.common.ui.TabView;
+import autotest.common.ui.NotifyManager;
+
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
+
+
+public class KeyUploadView extends TabView {
+    private FileUpload keyUpload;
+    private Button submitButton;
+    private FormPanel keyUploadForm;
+
+    protected String fileUploadName = "key";
+    protected String uploadViewName = "view_key";
+    protected String submitButtonName = "view_submit_key";
+    protected String rpcName = "";
+    protected String rpcArgName = "key";
+    protected String successMessage = "Key uploaded.";
+
+    @Override
+    public String getElementId() {
+        return "key";
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        keyUpload = new FileUpload();
+        keyUpload.setName(fileUploadName);
+
+        keyUploadForm = new FormPanel();
+        keyUploadForm.setAction(JsonRpcProxy.AFE_BASE_URL + "upload/");
+        keyUploadForm.setEncoding(FormPanel.ENCODING_MULTIPART);
+        keyUploadForm.setMethod(FormPanel.METHOD_POST);
+        keyUploadForm.setWidget(keyUpload);
+
+        submitButton = new Button("Submit", new ClickHandler() {
+            public void onClick(ClickEvent event) {
+                keyUploadForm.submit();
+            }
+        });
+
+        keyUploadForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {
+            public void onSubmitComplete(SubmitCompleteEvent event) {
+                String fileName = event.getResults();
+                JSONObject params = new JSONObject();
+                params.put(rpcArgName, new JSONString(fileName));
+                rpcCall(params);
+            }
+        });
+
+        addWidget(keyUploadForm, uploadViewName);
+        addWidget(submitButton, submitButtonName);
+    }
+
+    public void rpcCall(JSONObject params) {
+        JsonRpcProxy rpcProxy = JsonRpcProxy.getProxy();
+        rpcProxy.rpcCall(rpcName, params, new JsonRpcCallback() {
+            @Override
+            public void onSuccess(JSONValue result) {
+                NotifyManager.getInstance().showMessage(successMessage);
+            }
+        });
+    }
+}
diff --git a/frontend/client/src/autotest/moblab/LaunchControlKeyView.java b/frontend/client/src/autotest/moblab/LaunchControlKeyView.java
new file mode 100644
index 0000000..f193b50
--- /dev/null
+++ b/frontend/client/src/autotest/moblab/LaunchControlKeyView.java
@@ -0,0 +1,21 @@
+package autotest.moblab;
+
+public class LaunchControlKeyView extends KeyUploadView {
+
+    @Override
+    public String getElementId() {
+        return "launch_control_key";
+    }
+
+    @Override
+    public void initialize() {
+        fileUploadName = "launchcontrolkey";
+        uploadViewName = "view_launch_control_key";
+        submitButtonName = "view_submit_launch_control_key";
+        rpcName = "set_launch_control_key";
+        rpcArgName = "launch_control_key";
+        successMessage = "Launch Control Key uploaded.";
+
+        super.initialize();
+    }
+}
diff --git a/frontend/client/src/autotest/moblab/MoblabSetupClient.java b/frontend/client/src/autotest/moblab/MoblabSetupClient.java
index 3b07b15..e75982c 100644
--- a/frontend/client/src/autotest/moblab/MoblabSetupClient.java
+++ b/frontend/client/src/autotest/moblab/MoblabSetupClient.java
@@ -12,6 +12,7 @@
 public class MoblabSetupClient implements EntryPoint {
     private ConfigSettingsView configSettingsView;
     private BotoKeyView botoKeyView;
+    private LaunchControlKeyView launchControlKeyView;
 
     public CustomTabPanel mainTabPanel = new CustomTabPanel();
 
@@ -24,8 +25,10 @@
 
         configSettingsView = new ConfigSettingsView();
         botoKeyView = new BotoKeyView();
+        launchControlKeyView = new LaunchControlKeyView();
         mainTabPanel.addTabView(configSettingsView);
         mainTabPanel.addTabView(botoKeyView);
+        mainTabPanel.addTabView(launchControlKeyView);
 
         final RootPanel rootPanel = RootPanel.get("tabs");
         rootPanel.add(mainTabPanel);
@@ -33,4 +36,4 @@
         rootPanel.setStyleName("");
     }
 
-}
\ No newline at end of file
+}
diff --git a/frontend/client/src/autotest/public/MoblabSetupClient.html b/frontend/client/src/autotest/public/MoblabSetupClient.html
index 7f1599b..6afd4d7 100644
--- a/frontend/client/src/autotest/public/MoblabSetupClient.html
+++ b/frontend/client/src/autotest/public/MoblabSetupClient.html
@@ -25,9 +25,16 @@
         <span id="view_boto_key"></span>
         <span id="view_submit_boto_key"></span>
       </div>
+
+      <div id="launch_control_key" title="Launch Control Key">
+        <span class="field-name">Launch Control Key: </span>
+        <span id="view_launch_control_key"></span>
+        <span id="view_submit_launch_control_key"></span>
+      </div>
+
     </div>
 
     <br>
     <div id="error_log"></div>
   </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/server/hosts/moblab_host.py b/server/hosts/moblab_host.py
index 010f683..7f0faff 100644
--- a/server/hosts/moblab_host.py
+++ b/server/hosts/moblab_host.py
@@ -24,6 +24,7 @@
         '(?P<mac>[0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])')
 MOBLAB_IMAGE_STORAGE = '/mnt/moblab/static'
 MOBLAB_BOTO_LOCATION = '/home/moblab/.boto'
+MOBLAB_LAUNCH_CONTROL_KEY_LOCATION = '/home/moblab/.launch_control_key'
 MOBLAB_AUTODIR = '/usr/local/autodir'
 DHCPD_LEASE_FILE = '/var/lib/dhcp/dhcpd.leases'
 MOBLAB_SERVICES = ['moblab-scheduler-init',