Acloud to pull fetch cvd wrapper metrics when available

Test: e2e test and check report (acloud-dev create
--base-instance-num=51 --host fl2-arm-04.atc.google.com --host-user
android-test-admin --branch git_master --build-target
aosp_cf_arm64_only_phone_hwasan-userdebug  --build-id 9212898
--remote-fetch --service-account-json-private-key-path
/usr/local/google/home/yuchenhe/key.json  --fetch-cvd-wrapper
GOOGLE_APPLICATION_CREDENTIALS=/home/shared/cloudsql_service_account.json,CACHE_CONFIG=/home/shared/cache.properties,java,-jar,/home/android-test-admin/FetchCvdWrapper_deploy.jar
--report_file report.json -vv)
Bug: 254132128

Change-Id: I724064de48dbf379e0b2ddc7e916cfea04aa34f6
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 83b7139..890f799 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -295,7 +295,6 @@
                     "To connect with hostname, erase the extra_args_ssh_tunnel: %s",
                     extra_args_ssh_tunnel)
                 extra_args_ssh_tunnel=""
-
             if autoconnect and reporter.status == report.Status.SUCCESS:
                 forwarded_ports = _EstablishAdbVncConnections(
                     device.gce_hostname or ip, vnc_ports, adb_ports,
@@ -323,6 +322,10 @@
                     extra_args_ssh_tunnel=extra_args_ssh_tunnel)
             if device.instance_name in logs:
                 device_dict[constants.LOGS] = logs[device.instance_name]
+            if hasattr(device_factory, 'GetFetchCvdWrapperLogIfExist'):
+                fetch_cvd_wrapper_log = device_factory.GetFetchCvdWrapperLogIfExist()
+                if fetch_cvd_wrapper_log:
+                    device_dict["fetch_cvd_wrapper_log"] = fetch_cvd_wrapper_log
             if device.instance_name in failures:
                 reporter.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR)
                 if device.stage:
diff --git a/public/actions/common_operations_test.py b/public/actions/common_operations_test.py
index 746a9eb..51bb228 100644
--- a/public/actions/common_operations_test.py
+++ b/public/actions/common_operations_test.py
@@ -79,6 +79,9 @@
                                  "gcs_bucket_build_id": self.BUILD_ID})
         self.Patch(self.device_factory, "GetLogs",
                    return_value={self.INSTANCE: self.LOGS})
+        self.Patch(
+            self.device_factory,
+            "GetFetchCvdWrapperLogIfExist", return_value={})
 
     @staticmethod
     def _CreateCfg():
@@ -260,6 +263,32 @@
         expected_result = constants.GCE_QUOTA_ERROR
         self.assertEqual(common_operations._GetErrorType(error), expected_result)
 
+    def testCreateDevicesWithFetchCvdWrapper(self):
+        """Test Create Devices with FetchCvdWrapper."""
+        self.Patch(
+            self.device_factory,
+            "GetFetchCvdWrapperLogIfExist", return_value={"fetch_log": "abc"})
+        cfg = self._CreateCfg()
+        _report = common_operations.CreateDevices(self.CMD, cfg,
+                                                  self.device_factory, 1,
+                                                  constants.TYPE_CF)
+        self.assertEqual(_report.command, self.CMD)
+        self.assertEqual(_report.status, report.Status.SUCCESS)
+        self.assertEqual(
+            _report.data,
+            {"devices": [{
+                "ip": self.IP.external + ":6520",
+                "instance_name": self.INSTANCE,
+                "branch": self.BRANCH,
+                "build_id": self.BUILD_ID,
+                "build_target": self.BUILD_TARGET,
+                "gcs_bucket_build_id": self.BUILD_ID,
+                "logs": self.LOGS,
+                "fetch_cvd_wrapper_log": {
+                    "fetch_log": "abc"
+                },
+            }]})
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/public/actions/remote_host_cf_device_factory.py b/public/actions/remote_host_cf_device_factory.py
index 23ece6f..abbe6f0 100644
--- a/public/actions/remote_host_cf_device_factory.py
+++ b/public/actions/remote_host_cf_device_factory.py
@@ -16,6 +16,7 @@
 cuttlefish instances on a remote host."""
 
 import glob
+import json
 import logging
 import os
 import posixpath as remote_path
@@ -428,3 +429,24 @@
             A dictionary that maps instance names to lists of report.LogFile.
         """
         return self._all_logs
+
+    def GetFetchCvdWrapperLogIfExist(self):
+        """Get FetchCvdWrapper log if exist.
+
+        Returns:
+            A dictionary that includes FetchCvdWrapper logs.
+        """
+        if not self._avd_spec.fetch_cvd_wrapper:
+            return {}
+        path = os.path.join(self._GetInstancePath(), "fetch_cvd_wrapper_log.json")
+        ssh_cmd = self._ssh.GetBaseCmd(constants.SSH_BIN) + " cat " + path
+        proc = subprocess.run(ssh_cmd, shell=True, capture_output=True,
+                              check=False)
+        if proc.stderr:
+            logger.debug("`%s` stderr: %s", ssh_cmd, proc.stderr.decode())
+        if proc.stdout:
+            try:
+                return json.loads(proc.stdout)
+            except ValueError as e:
+                return {"status": "FETCH_WRAPPER_REPORT_PARSE_ERROR"}
+        return {}