Merge "Do not check all CF configs if instance names are specified"
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index b965a99..e89376d 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -204,6 +204,11 @@
             int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
             blank_data_disk_size_gb)
 
+        # Record the system build and kernel build into metadata.
+        self._RecordSystemAndKernelInfo(avd_spec, system_build_id,
+                                        system_build_target, kernel_build_id,
+                                        kernel_build_target)
+
         if avd_spec and avd_spec.instance_name_to_reuse:
             self._ip = self._ReusingGceInstance(avd_spec)
         else:
@@ -243,6 +248,31 @@
             self._all_failures[instance] = e
             return instance
 
+    def _RecordSystemAndKernelInfo(self, avd_spec, system_build_id,
+                                   system_build_target, kernel_build_id,
+                                   kernel_build_target):
+        """Rocord the system build info and kernel build info into metadata.
+
+        Args:
+            avd_spec: An AVDSpec instance.
+            system_build_id: A string, build id for the system image.
+            system_build_target: Target name for the system image,
+                                e.g. "cf_x86_phone-userdebug"
+            kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
+            kernel_build_target: String, Kernel build target name.
+        """
+        if avd_spec:
+            system_build_id = avd_spec.system_build_info.get(constants.BUILD_ID)
+            system_build_target = avd_spec.system_build_info.get(constants.BUILD_TARGET)
+            kernel_build_id = avd_spec.kernel_build_info.get(constants.BUILD_ID)
+            kernel_build_target = avd_spec.kernel_build_info.get(constants.BUILD_TARGET)
+        if system_build_id and system_build_target:
+            self._metadata.update({"system_build_id": system_build_id})
+            self._metadata.update({"system_build_target": system_build_target})
+        if kernel_build_id and kernel_build_target:
+            self._metadata.update({"kernel_build_id": kernel_build_id})
+            self._metadata.update({"kernel_build_target": kernel_build_target})
+
     def _GetLaunchCvdArgs(self, avd_spec=None, blank_data_disk_size_gb=None,
                           kernel_build=None, decompress_kernel=None):
         """Get launch_cvd args.
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index bdd54bc..3fd5428 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -168,6 +168,8 @@
         created_subprocess.poll = mock.MagicMock(return_value=0)
         created_subprocess.returncode = 0
         created_subprocess.communicate = mock.MagicMock(return_value=('', ''))
+        self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
+                   "_RecordSystemAndKernelInfo")
         self.Patch(subprocess, "Popen", return_value=created_subprocess)
         self.Patch(subprocess, "check_call")
         self.Patch(os, "chmod")
@@ -227,6 +229,37 @@
             gpu=self.GPU,
             tags=None)
 
+    def testRecordSystemAndKernelInfo(self):
+        """Test RecordSystemAndKernelInfo"""
+        system_build_id = "system_id"
+        system_build_target = "system_target"
+        kernel_build_id = "kernel_id"
+        kernel_build_target = "kernel_target"
+        fake_avd_spec = mock.MagicMock()
+        fake_avd_spec.system_build_info = {constants.BUILD_ID: system_build_id,
+                                           constants.BUILD_TARGET: system_build_target}
+        fake_avd_spec.kernel_build_info = {constants.BUILD_ID: kernel_build_id,
+                                           constants.BUILD_TARGET: kernel_build_target}
+        expected_metadata = dict()
+        expected_metadata.update(self.METADATA)
+        expected_metadata.update({"system_build_id": system_build_id})
+        expected_metadata.update({"system_build_target": system_build_target})
+        expected_metadata.update({"kernel_build_id": kernel_build_id})
+        expected_metadata.update({"kernel_build_target": kernel_build_target})
+
+        # Test record metadata with avd_spec for acloud create
+        self.cvd_compute_client_multi_stage._RecordSystemAndKernelInfo(
+            fake_avd_spec, system_build_id=None, system_build_target=None,
+            kernel_build_id=None, kernel_build_target=None)
+        self.assertEqual(self.cvd_compute_client_multi_stage._metadata, expected_metadata)
+
+        # Test record metadata with build info of system image and kerenel image for
+        # acloud create_cf
+        self.cvd_compute_client_multi_stage._RecordSystemAndKernelInfo(
+            None, system_build_id, system_build_target,
+            kernel_build_id, kernel_build_target)
+        self.assertEqual(self.cvd_compute_client_multi_stage._metadata, expected_metadata)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/internal/lib/cvd_runtime_config.py b/internal/lib/cvd_runtime_config.py
index 3dfbbc1..36e068d 100644
--- a/internal/lib/cvd_runtime_config.py
+++ b/internal/lib/cvd_runtime_config.py
@@ -19,7 +19,7 @@
 
 from acloud import errors
 
-_CFG_KEY_ADB_CONNECTOR_BINARY = "adb_connector_binary"
+_CFG_KEY_CROSVM_BINARY = "crosvm_binary"
 _CFG_KEY_X_RES = "x_res"
 _CFG_KEY_Y_RES = "y_res"
 _CFG_KEY_DPI = "dpi"
@@ -112,9 +112,9 @@
         self._x_res = self._config_dict.get(_CFG_KEY_X_RES)
         self._y_res = self._config_dict.get(_CFG_KEY_Y_RES)
         self._dpi = self._config_dict.get(_CFG_KEY_DPI)
-        adb_connector = self._config_dict.get(_CFG_KEY_ADB_CONNECTOR_BINARY)
-        self._cvd_tools_path = (os.path.dirname(adb_connector)
-                                if adb_connector else None)
+        crosvm_bin = self._config_dict.get(_CFG_KEY_CROSVM_BINARY)
+        self._cvd_tools_path = (os.path.dirname(crosvm_bin)
+                                if crosvm_bin else None)
 
         # Below properties will be collected inside of instance id node if there
         # are more than one instance.
diff --git a/internal/lib/cvd_runtime_config_test.py b/internal/lib/cvd_runtime_config_test.py
index c201f16..060307a 100644
--- a/internal/lib/cvd_runtime_config_test.py
+++ b/internal/lib/cvd_runtime_config_test.py
@@ -58,7 +58,7 @@
  },
  "enable_webrtc" : true,
  "vnc_server_binary" : "/home/vsoc-01/bin/vnc_server",
- "adb_connector_binary" : "/home/vsoc-01/bin/adb_connector",
+ "crosvm_binary" : "/home/vsoc-01/bin/crosvm",
  "webrtc_assets_dir" : "/home/vsoc-01/usr/share/webrtc/assets",
  "webrtc_binary" : "/home/vsoc-01/bin/webRTC",
  "webrtc_certs_dir" : "/home/vsoc-01/usr/share/webrtc/certs",
diff --git a/list/instance.py b/list/instance.py
index 7150cde..10063c4 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -762,9 +762,11 @@
 
         default_vnc_port = utils.AVD_PORT_DICT[avd_type].vnc_port
         default_adb_port = utils.AVD_PORT_DICT[avd_type].adb_port
+        # TODO(165888525): Align the SSH tunnel for the order of adb port and
+        # vnc port.
         re_pattern = re.compile(_RE_SSH_TUNNEL_PATTERN %
-                                (_RE_GROUP_VNC, default_vnc_port,
-                                 _RE_GROUP_ADB, default_adb_port, ip))
+                                (_RE_GROUP_ADB, default_adb_port,
+                                 _RE_GROUP_VNC, default_vnc_port, ip))
         adb_port = None
         vnc_port = None
         process_output = utils.CheckOutput(constants.COMMAND_PS)
diff --git a/list/instance_test.py b/list/instance_test.py
index 7efc4f3..f5d2575 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -39,8 +39,8 @@
                       "/fake_ps_2 --fake arg \n"
                       "/usr/bin/ssh -i ~/.ssh/acloud_rsa "
                       "-o UserKnownHostsFile=/dev/null "
-                      "-o StrictHostKeyChecking=no -L 12345:127.0.0.1:6444 "
-                      "-L 54321:127.0.0.1:6520 -N -f -l user 1.1.1.1")
+                      "-o StrictHostKeyChecking=no -L 54321:127.0.0.1:6520 "
+                      "-L 12345:127.0.0.1:6444 -N -f -l user 1.1.1.1")
     PS_LAUNCH_CVD = b("Sat Nov 10 21:55:10 2018 /fake_path/bin/run_cvd ")
     PS_RUNTIME_CF_CONFIG = {"x_res": "1080", "y_res": "1920", "dpi": "480"}
     GCE_INSTANCE = {
diff --git a/powerwash/powerwash.py b/powerwash/powerwash.py
index f2bf338..fcd8853 100644
--- a/powerwash/powerwash.py
+++ b/powerwash/powerwash.py
@@ -16,30 +16,58 @@
 This command will powerwash the AVD from a remote instance.
 """
 
-from __future__ import print_function
+import logging
+import subprocess
 
 from acloud import errors
+from acloud.internal import constants
+from acloud.internal.lib import utils
+from acloud.internal.lib.ssh import Ssh
+from acloud.internal.lib.ssh import IP
 from acloud.list import list as list_instances
 from acloud.public import config
 from acloud.public import report
 
 
-def PowerwashFromInstance(instance, instance_id):
+logger = logging.getLogger(__name__)
+
+
+def PowerwashFromInstance(cfg, instance, instance_id):
     """Powerwash AVD from remote CF instance.
 
     Args:
+        cfg: AcloudConfig object.
         instance: list.Instance() object.
         instance_id: Integer of the instance id.
 
     Returns:
         A Report instance.
     """
-    # TODO(162382338): rewrite this function to powerwash AVD from the remote instance.
-    print("We will powerwash AVD id (%s) from the instance: %s."
-          % (instance_id, instance.name))
+    ssh = Ssh(ip=IP(ip=instance.ip),
+              user=constants.GCE_USER,
+              ssh_private_key_path=cfg.ssh_private_key_path,
+              extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
+    logger.info("Start to powerwash AVD id (%s) from the instance: %s.",
+                instance_id, instance.name)
+    PowerwashDevice(ssh, instance_id)
     return report.Report(command="powerwash")
 
 
+def PowerwashDevice(ssh, instance_id):
+    """Powerwash AVD with the instance id.
+
+    Args:
+        ssh: Ssh object.
+        instance_id: Integer of the instance id.
+    """
+    ssh_command = "./bin/powerwash_cvd --instance_num=%d" % (instance_id)
+    try:
+        ssh.Run(ssh_command)
+    except (subprocess.CalledProcessError, errors.DeviceConnectionError) as e:
+        logger.debug(str(e))
+        utils.PrintColorString(str(e), utils.TextColors.FAIL)
+
+
 def Run(args):
     """Run powerwash.
 
@@ -50,13 +78,12 @@
 
     Returns:
         A Report instance.
-
-    Raises:
-        errors.CommandArgError: Lack the instance_name in args.
     """
     cfg = config.GetAcloudConfig(args)
     if args.instance_name:
         instance = list_instances.GetInstancesFromInstanceNames(
             cfg, [args.instance_name])
-        return PowerwashFromInstance(instance[0], args.instance_id)
-    raise errors.CommandArgError("Please assign the '--instance-name' in your command.")
+        return PowerwashFromInstance(cfg, instance[0], args.instance_id)
+    return PowerwashFromInstance(cfg,
+                                 list_instances.ChooseOneRemoteInstance(cfg),
+                                 args.instance_id)