Honor the results_storage_server parameter in moblab.

By default, the results_storage_server in global_config.ini is empty
now. Both lab and moblab could set the value and take effect. If not
set, the lab takes the original default value, and moblab takes the
image bucket uri.

BUG=chromium:621212
TEST=unit test.

Change-Id: Ifd712b53bcffa76af17db8cd4d87947323291b08
Reviewed-on: https://chromium-review.googlesource.com/356126
Commit-Ready: Michael Tang <ntang@chromium.org>
Tested-by: Michael Tang <ntang@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Reviewed-by: Michael Tang <ntang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/386992
Trybot-Ready: Michael Tang <ntang@chromium.org>
Commit-Queue: Michael Tang <ntang@chromium.org>
diff --git a/client/common_lib/site_utils.py b/client/common_lib/site_utils.py
index cfbd776..3c111da 100644
--- a/client/common_lib/site_utils.py
+++ b/client/common_lib/site_utils.py
@@ -161,6 +161,25 @@
     return interface_link.split()[1]
 
 
+def get_moblab_id():
+    """Gets the moblab random id.
+
+    The random id file is cached on disk. If it does not exist, a new file is
+    created the first time.
+
+    @returns the moblab random id.
+    """
+    moblab_id_filepath = '/home/moblab/.moblab_id'
+    if os.path.exists(moblab_id_filepath):
+        with open(moblab_id_filepath, 'r') as moblab_id_file:
+            random_id = moblab_id_file.read()
+    else:
+        random_id = uuid.uuid1()
+        with open(moblab_id_filepath, 'w') as moblab_id_file:
+            moblab_id_file.write('%s' % random_id)
+    return random_id
+
+
 def get_offload_gsuri():
     """Return the GSURI to offload test results to.
 
@@ -173,19 +192,18 @@
 
     @returns gsuri to offload test results to.
     """
+    # For non-moblab, use results_storage_server or default.
     if not lsbrelease_utils.is_moblab():
         return DEFAULT_OFFLOAD_GSURI
-    moblab_id_filepath = '/home/moblab/.moblab_id'
-    if os.path.exists(moblab_id_filepath):
-        with open(moblab_id_filepath, 'r') as moblab_id_file:
-            random_id = moblab_id_file.read()
-    else:
-        random_id = uuid.uuid1()
-        with open(moblab_id_filepath, 'w') as moblab_id_file:
-            moblab_id_file.write('%s' % random_id)
+
+    # For moblab, use results_storage_server or image_storage_server as bucket
+    # name and mac-address/moblab_id as path.
+    gsuri = DEFAULT_OFFLOAD_GSURI
+    if not gsuri:
+        gsuri = CONFIG.get_config_value('CROS', 'image_storage_server')
+
     return '%sresults/%s/%s/' % (
-            CONFIG.get_config_value('CROS', 'image_storage_server'),
-            get_interface_mac_address(MOBLAB_ETH), random_id)
+            gsuri, get_interface_mac_address(MOBLAB_ETH), get_moblab_id())
 
 
 # TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
diff --git a/client/common_lib/site_utils_unittest.py b/client/common_lib/site_utils_unittest.py
index 5c2967e..7691765 100755
--- a/client/common_lib/site_utils_unittest.py
+++ b/client/common_lib/site_utils_unittest.py
@@ -4,6 +4,8 @@
 import unittest
 
 import common
+from autotest_lib.client.common_lib import lsbrelease_utils
+from autotest_lib.client.common_lib import site_utils
 from autotest_lib.client.common_lib import utils
 from autotest_lib.client.common_lib.test_utils import mock
 
@@ -192,5 +194,55 @@
             self.assertEqual(result, utils.parse_launch_control_target(target))
 
 
+class GetOffloaderUriTest(unittest.TestCase):
+    """Test get_offload_gsuri function."""
+    _IMAGE_STORAGE_SERVER = 'gs://test_image_bucket'
+
+    def test_get_default_lab_offload_gsuri(self):
+        """Test default lab offload gsuri ."""
+        god = mock.mock_god()
+        god.mock_up(utils.CONFIG, 'CONFIG')
+        god.stub_function_to_return(lsbrelease_utils, 'is_moblab', False)
+        self.assertEqual(utils.DEFAULT_OFFLOAD_GSURI,
+                utils.get_offload_gsuri())
+
+        god.check_playback()
+
+    def test_get_default_moblab_offload_gsuri(self):
+        """Test default lab offload gsuri ."""
+        god = mock.mock_god()
+        god.mock_up(utils.CONFIG, 'CONFIG')
+        god.stub_function_to_return(lsbrelease_utils, 'is_moblab', True)
+        utils.CONFIG.get_config_value.expect_call(
+                'CROS', 'image_storage_server').and_return(
+                        self._IMAGE_STORAGE_SERVER)
+        god.stub_function_to_return(site_utils, 'get_interface_mac_address',
+                'test_mac')
+        god.stub_function_to_return(site_utils, 'get_moblab_id', 'test_id')
+        expected_gsuri = '%sresults/%s/%s/' % (
+                self._IMAGE_STORAGE_SERVER, 'test_mac', 'test_id')
+        cached_gsuri = site_utils.DEFAULT_OFFLOAD_GSURI
+        site_utils.DEFAULT_OFFLOAD_GSURI = None
+        gsuri = utils.get_offload_gsuri()
+        site_utils.DEFAULT_OFFLOAD_GSURI = cached_gsuri
+        self.assertEqual(expected_gsuri, gsuri)
+
+        god.check_playback()
+
+    def test_get_moblab_offload_gsuri(self):
+        """Test default lab offload gsuri ."""
+        god = mock.mock_god()
+        god.mock_up(utils.CONFIG, 'CONFIG')
+        god.stub_function_to_return(lsbrelease_utils, 'is_moblab', True)
+        god.stub_function_to_return(site_utils, 'get_interface_mac_address',
+                'test_mac')
+        god.stub_function_to_return(site_utils, 'get_moblab_id', 'test_id')
+        gsuri = '%sresults/%s/%s/' % (
+                utils.DEFAULT_OFFLOAD_GSURI, 'test_mac', 'test_id')
+        self.assertEqual(gsuri, utils.get_offload_gsuri())
+
+        god.check_playback()
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/contrib/show_offload_failures b/contrib/show_offload_failures
index 1997527..b5fe5ac 100755
--- a/contrib/show_offload_failures
+++ b/contrib/show_offload_failures
@@ -21,9 +21,8 @@
 
 GET_GSURI="
 import common
-from autotest_lib.client.common_lib import global_config
-print global_config.global_config.get_config_value(
-        'CROS', 'results_storage_server').strip('/')
+from autotest_lib.client.common_lib import utils
+print utils.get_offload_gsuri().strip('/')
 "
 GSURI=$(cd .. ; python -c "$GET_GSURI")
 (
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index e8f8a4e..aecc1a1 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -548,7 +548,8 @@
     value =_CONFIG.get_config_value('CROS', _IMAGE_STORAGE_SERVER)
     if value is not None:
         cloud_storage_info[_IMAGE_STORAGE_SERVER] = value
-    value =_CONFIG.get_config_value('CROS', _RESULT_STORAGE_SERVER)
+    value = _CONFIG.get_config_value('CROS', _RESULT_STORAGE_SERVER,
+            default=None)
     if value is not None:
         cloud_storage_info[_RESULT_STORAGE_SERVER] = value
 
@@ -656,7 +657,8 @@
             valid, details = _is_valid_bucket_url(
                 key_id, key_secret, cloud_storage_info[_IMAGE_STORAGE_SERVER])
 
-        if valid:
+        # allows result bucket to be empty.
+        if valid and cloud_storage_info[_RESULT_STORAGE_SERVER]:
             valid, details = _is_valid_bucket_url(
                 key_id, key_secret, cloud_storage_info[_RESULT_STORAGE_SERVER])
     return (valid, details)
diff --git a/frontend/afe/site_rpc_interface_unittest.py b/frontend/afe/site_rpc_interface_unittest.py
index 54afc6b..2e35dcd 100755
--- a/frontend/afe/site_rpc_interface_unittest.py
+++ b/frontend/afe/site_rpc_interface_unittest.py
@@ -422,7 +422,8 @@
         config_mock.get_config_value(
             'CROS', 'image_storage_server').AndReturn('gs://bucket1')
         config_mock.get_config_value(
-            'CROS', 'results_storage_server').AndReturn('gs://bucket2')
+            'CROS', 'results_storage_server', default=None).AndReturn(
+                    'gs://bucket2')
         self.mox.StubOutWithMock(site_rpc_interface, '_get_boto_config')
         site_rpc_interface._get_boto_config().AndReturn(config_mock)
         config_mock.sections().AndReturn(['Credentials', 'b'])
diff --git a/frontend/client/src/autotest/moblab/rpc/CloudStorageInfo.java b/frontend/client/src/autotest/moblab/rpc/CloudStorageInfo.java
index 74dc8c6..d018dc5 100644
--- a/frontend/client/src/autotest/moblab/rpc/CloudStorageInfo.java
+++ b/frontend/client/src/autotest/moblab/rpc/CloudStorageInfo.java
@@ -109,6 +109,8 @@
     }
     if (resultStorageServer != null) {
       object.put(JSON_FIELD_RESULT_STORAGE_URL, new JSONString(resultStorageServer));
+    } else {
+      object.put(JSON_FIELD_RESULT_STORAGE_URL, new JSONString(""));
     }
     object.put(JSON_FIELD_USE_EXISTING_BOTO_FILE, JSONBoolean.getInstance(useExistingBotoFile));
     return object;
diff --git a/frontend/client/src/autotest/moblab/wizard/CloudStorageCard.java b/frontend/client/src/autotest/moblab/wizard/CloudStorageCard.java
index edd6e3b..7ef346b 100644
--- a/frontend/client/src/autotest/moblab/wizard/CloudStorageCard.java
+++ b/frontend/client/src/autotest/moblab/wizard/CloudStorageCard.java
@@ -13,6 +13,7 @@
 import autotest.moblab.rpc.MoblabRpcCallbacks;
 import autotest.moblab.rpc.MoblabRpcHelper;
 import autotest.moblab.rpc.OperationStatus;
+import autotest.common.ui.ToolTip;
 
 import java.util.HashMap;
 
@@ -88,13 +89,17 @@
 
       // Row for result bucket url.
       row++;
-      layoutTable.setWidget(row, 0, new Label("Result Bucket URL"));
+      layoutTable.setWidget(row, 0, new Label("Result Bucket URL(optional)"));
       url = cloudStorageInfo.getResultStorageServer();
       layoutTable.setWidget(row, 1,
           createValueFieldWidget(CloudStorageInfo.JSON_FIELD_RESULT_STORAGE_URL, url));
       if (url != null && ConfigWizard.Mode.View == getMode()) {
         Anchor link = Utils.createGoogleStorageHttpUrlLink("link", url);
         layoutTable.setWidget(row, 2, link);
+      } else if (ConfigWizard.Mode.Edit == getMode()){
+        ToolTip tip = new ToolTip( "?",
+          "If not specicifed, Molab will use the image bucket for result uploading.");
+        layoutTable.setWidget(row, 2, tip);
       }
 
       if (ConfigWizard.Mode.Edit == getMode()) {
@@ -131,10 +136,9 @@
     cloudStorageInfo.setResultStorageServer(
         getStringValueFieldValue(CloudStorageInfo.JSON_FIELD_RESULT_STORAGE_URL));
     // Image bucket and result bucket can not be empty.
-    if (cloudStorageInfo.getImageStorageServer() == null
-        || cloudStorageInfo.getResultStorageServer() == null) {
+    if (cloudStorageInfo.getImageStorageServer() == null) {
       callback.onValidationStatus(
-          new OperationStatus(false, "The bucket URL fields could not be empty"));
+          new OperationStatus(false, "The image bucket URL fields could not be empty"));
       return;
     }
 
diff --git a/frontend/client/src/autotest/public/afeclient.css b/frontend/client/src/autotest/public/afeclient.css
index 213a346..fb10bcf 100644
--- a/frontend/client/src/autotest/public/afeclient.css
+++ b/frontend/client/src/autotest/public/afeclient.css
@@ -430,5 +430,5 @@
 }
 
 .config-wizard .wizard-card-panel .wizard-card-property-value {
-  width: 200px;
+  width: 300px;
 }