Mark ab/7061308 as merged in stage.

Bug: 180401296
Merged-In: Ib7eabae2e55bddf387d486b1258ab638c77ff9cf
Change-Id: I7b4c436f3704a86e6f35eb50cf5d87f714fd44e3
diff --git a/Android.bp b/Android.bp
index 89bc8aa..c9191c5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["tools_acloud_license"],
+}
+
+// Added automatically by a large-scale-change
+// http://go/android-license-faq
+license {
+    name: "tools_acloud_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "LICENSE",
+    ],
+}
+
 python_defaults {
     name: "acloud_default",
     pkg_path: "acloud",
@@ -55,6 +72,7 @@
         "acloud_metrics",
         "acloud_proto",
         "acloud_public",
+        "acloud_restart",
         "acloud_setup",
         "py-apitools",
         "py-dateutil",
@@ -91,6 +109,7 @@
         "acloud_pull",
         "acloud_proto",
         "acloud_public",
+        "acloud_restart",
         "acloud_setup",
         "asuite_cc_client",
         "py-apitools",
@@ -205,6 +224,14 @@
 }
 
 python_library_host{
+    name: "acloud_restart",
+    defaults: ["acloud_default"],
+    srcs: [
+         "restart/*.py",
+    ],
+}
+
+python_library_host{
     name: "acloud_metrics",
     defaults: ["acloud_default"],
     srcs: [
diff --git a/README.md b/README.md
index 60d8796..f7fea24 100755
--- a/README.md
+++ b/README.md
@@ -221,4 +221,4 @@
 
 If you have any questions or feedback, contact [acloud@google.com](mailto:acloud@google.com).
 
-If you have any bugs or feature requests, [go/acloud-bug](http://go/acloud-bug).
+If you have any bugs or feature requests email them to [buganizer-system+419709@google.com](mailto:buganizer-system+419709@google.com)
\ No newline at end of file
diff --git a/create/avd_spec.py b/create/avd_spec.py
index f0ac5ad..af61f4b 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -125,6 +125,7 @@
         self._bootloader_build_info = None
         self._hw_property = None
         self._remote_host = None
+        self._gce_metadata = None
         self._host_user = None
         self._host_ssh_private_key_path = None
         # Create config instance for android_build_client to query build api.
@@ -230,7 +231,7 @@
         Raises:
             error.MalformedHWPropertyError: If hw_property_str is malformed.
         """
-        hw_dict = create_common.ParseHWPropertyArgs(hw_property_str)
+        hw_dict = create_common.ParseKeyValuePairArgs(hw_property_str)
         arg_hw_properties = {}
         for key, value in hw_dict.items():
             # Parsing HW properties int to avdspec.
@@ -317,6 +318,7 @@
         self._serial_log_file = args.serial_log_file
         self._emulator_build_id = args.emulator_build_id
         self._gpu = args.gpu
+        self._gce_metadata = create_common.ParseKeyValuePairArgs(args.gce_metadata)
 
         self._stable_cheeps_host_image_name = args.stable_cheeps_host_image_name
         self._stable_cheeps_host_image_project = args.stable_cheeps_host_image_project
@@ -467,6 +469,13 @@
             self._CheckCFBuildTarget(self._instance_type)
             local_image_path = utils.GetBuildEnvironmentVariable(
                 _ENV_ANDROID_PRODUCT_OUT)
+            # Since dir is provided, check that any images exist to ensure user
+            # didn't forget to 'make' before launch AVD.
+            image_list = glob.glob(os.path.join(local_image_path, "*.img"))
+            if not image_list:
+                raise errors.GetLocalImageError(
+                    "No image found(Did you choose a lunch target and run `m`?)"
+                    ": %s.\n " % local_image_path)
         else:
             local_image_path = local_image_arg
 
@@ -480,14 +489,6 @@
                                    utils.TextColors.WARNING)
         else:
             self._local_image_dir = local_image_path
-            # Since dir is provided, so checking that any images exist to ensure
-            # user didn't forget to 'make' before launch AVD.
-            image_list = glob.glob(os.path.join(self.local_image_dir, "*.img"))
-            if not image_list:
-                raise errors.GetLocalImageError(
-                    "No image found(Did you choose a lunch target and run `m`?)"
-                    ": %s.\n " % self.local_image_dir)
-
             try:
                 flavor_from_build_string = self._GetFlavorFromString(
                     utils.GetBuildEnvironmentVariable(constants.ENV_BUILD_TARGET))
@@ -909,3 +910,8 @@
     def no_pull_log(self):
         """Return no_pull_log."""
         return self._no_pull_log
+
+    @property
+    def gce_metadata(self):
+        """Return gce_metadata."""
+        return self._gce_metadata
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 836c3ae..c608344 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -92,15 +92,37 @@
         self.AvdSpec._ProcessLocalImageArgs(self.args)
         self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
 
-        # Specified --avd-type=goldfish --local_image without arg
-        self.Patch(utils, "GetBuildEnvironmentVariable",
-                   return_value="test_environ")
-        self.Patch(os.path, "isdir", return_value=True)
+        # Specified --local-image and --local-system-image with dirs
+        self.args.local_image = expected_image_dir
+        self.args.local_system_image = expected_image_dir
+        self.AvdSpec._avd_type = constants.TYPE_CF
+        self.AvdSpec._instance_type = constants.INSTANCE_TYPE_LOCAL
+        with mock.patch("os.path.isfile", return_value=False), \
+             mock.patch("os.path.isdir",
+                        side_effect=lambda path: path == expected_image_dir), \
+             mock.patch("acloud.create.avd_spec.utils."
+                        "GetBuildEnvironmentVariable",
+                        return_value="cf_x86_phone"):
+            self.AvdSpec._ProcessLocalImageArgs(self.args)
+        self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
+        self.assertEqual(self.AvdSpec._local_system_image_dir,
+                         expected_image_dir)
+
+        # Specified --avd-type=goldfish, --local_image, and
+        # --local-system-image without args
         self.args.local_image = constants.FIND_IN_BUILD_ENV
+        self.args.local_system_image = constants.FIND_IN_BUILD_ENV
         self.AvdSpec._avd_type = constants.TYPE_GF
         self.AvdSpec._instance_type = constants.INSTANCE_TYPE_LOCAL
-        self.AvdSpec._ProcessLocalImageArgs(self.args)
-        self.assertEqual(self.AvdSpec._local_image_dir, "test_environ")
+        with mock.patch("os.path.isdir",
+                        side_effect=lambda path: path == expected_image_dir), \
+             mock.patch("acloud.create.avd_spec.utils."
+                        "GetBuildEnvironmentVariable",
+                        return_value=expected_image_dir):
+            self.AvdSpec._ProcessLocalImageArgs(self.args)
+        self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
+        self.assertEqual(self.AvdSpec._local_system_image_dir,
+                         expected_image_dir)
 
     def testProcessImageArgs(self):
         """Test process image source."""
diff --git a/create/create_args.py b/create/create_args.py
index 63eaeea..ac359a9 100644
--- a/create/create_args.py
+++ b/create/create_args.py
@@ -429,6 +429,13 @@
         "provided. Select one gce instance to reuse if --reuse-gce is "
         "provided.")
     create_parser.add_argument(
+        "--gce-metadata",
+        type=str,
+        dest="gce_metadata",
+        default=None,
+        help="'GCE instance only' Record data into GCE instance metadata with "
+        "key-value pair format. e.g. id:12,name:unknown.")
+    create_parser.add_argument(
         "--host",
         type=str,
         dest="remote_host",
@@ -555,9 +562,8 @@
         raise errors.CheckPathError(
             "Specified path doesn't exist: %s" % args.local_instance_dir)
 
-    # TODO(b/133211308): Support TYPE_CF.
     if not (args.local_system_image is None or
-            args.avd_type == constants.TYPE_GF):
+            args.avd_type in (constants.TYPE_CF, constants.TYPE_GF)):
         raise errors.UnsupportedCreateArgs("%s instance does not support "
                                            "--local-system-image" %
                                            args.avd_type)
@@ -647,7 +653,7 @@
     if args.adb_port:
         utils.CheckPortFree(args.adb_port)
 
-    hw_properties = create_common.ParseHWPropertyArgs(args.hw_property)
+    hw_properties = create_common.ParseKeyValuePairArgs(args.hw_property)
     for key in hw_properties:
         if key not in constants.HW_PROPERTIES:
             raise errors.InvalidHWPropertyError(
diff --git a/create/create_common.py b/create/create_common.py
index ff56965..e18e58b 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -29,7 +29,7 @@
 logger = logging.getLogger(__name__)
 
 
-def ParseHWPropertyArgs(dict_str, item_separator=",", key_value_separator=":"):
+def ParseKeyValuePairArgs(dict_str, item_separator=",", key_value_separator=":"):
     """Helper function to initialize a dict object from string.
 
     e.g.
@@ -47,9 +47,9 @@
     Raises:
         error.MalformedDictStringError: If dict_str is malformed.
     """
-    hw_dict = {}
+    args_dict = {}
     if not dict_str:
-        return hw_dict
+        return args_dict
 
     for item in dict_str.split(item_separator):
         if key_value_separator not in item:
@@ -59,9 +59,9 @@
         if not value or not key:
             raise errors.MalformedDictStringError(
                 "Missing key or value in %s, expecting form of 'a:b'" % item)
-        hw_dict[key.strip()] = value.strip()
+        args_dict[key.strip()] = value.strip()
 
-    return hw_dict
+    return args_dict
 
 
 def GetCvdHostPackage():
diff --git a/create/create_common_test.py b/create/create_common_test.py
index a18f561..0cfaea7 100644
--- a/create/create_common_test.py
+++ b/create/create_common_test.py
@@ -48,23 +48,23 @@
 
     # pylint: disable=protected-access
     def testProcessHWPropertyWithInvalidArgs(self):
-        """Test ParseHWPropertyArgs with invalid args."""
+        """Test ParseKeyValuePairArgs with invalid args."""
         # Checking wrong property value.
         args_str = "cpu:3,disk:"
         with self.assertRaises(errors.MalformedDictStringError):
-            create_common.ParseHWPropertyArgs(args_str)
+            create_common.ParseKeyValuePairArgs(args_str)
 
         # Checking wrong property format.
         args_str = "cpu:3,disk"
         with self.assertRaises(errors.MalformedDictStringError):
-            create_common.ParseHWPropertyArgs(args_str)
+            create_common.ParseKeyValuePairArgs(args_str)
 
     def testParseHWPropertyStr(self):
-        """Test ParseHWPropertyArgs."""
+        """Test ParseKeyValuePairArgs."""
         expected_dict = {"cpu": "2", "resolution": "1080x1920", "dpi": "240",
                          "memory": "4g", "disk": "4g"}
         args_str = "cpu:2,resolution:1080x1920,dpi:240,memory:4g,disk:4g"
-        result_dict = create_common.ParseHWPropertyArgs(args_str)
+        result_dict = create_common.ParseKeyValuePairArgs(args_str)
         self.assertTrue(expected_dict == result_dict)
 
     def testGetCvdHostPackage(self):
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 322284d..e40d4b6 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -33,9 +33,23 @@
 
 To delete the local instance, we will call stop_cvd with the environment variable
 [CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json.
+
+To run this program outside of a build environment, the following setup is
+required.
+- One of the local tool directories is a decompressed cvd host package,
+  i.e., cvd-host_package.tar.gz.
+- If the instance doesn't require mixed images, the local image directory
+  should be an unzipped update package, i.e., <target>-img-<build>.zip,
+  which contains a super image.
+- If the instance requires mixed images, the local image directory should
+  be an unzipped target files package, i.e., <target>-target_files-<build>.zip,
+  which contains misc info and images not packed into a super image.
+- If the instance requires mixed images, one of the local tool directories
+  should be an unzipped OTA tools package, i.e., otatools.zip.
 """
 
 import collections
+import glob
 import logging
 import os
 import subprocess
@@ -46,6 +60,7 @@
 from acloud.create import base_avd_create
 from acloud.create import create_common
 from acloud.internal import constants
+from acloud.internal.lib import ota_tools
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
 from acloud.list import list as list_instance
@@ -55,6 +70,11 @@
 
 logger = logging.getLogger(__name__)
 
+_SYSTEM_IMAGE_NAME = "system.img"
+_MISC_INFO_FILE_NAME = "misc_info.txt"
+_TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"
+_TARGET_FILES_META_DIR_NAME = "META"
+_MIXED_SUPER_IMAGE_NAME = "mixed_super.img"
 _CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
                         "-memory_mb %s -run_adb_connector=%s "
                         "-system_image_dir %s -instance_dir %s "
@@ -69,6 +89,7 @@
                                "-start_webrtc=true "
                                "-webrtc_public_ip=%s" % constants.LOCALHOST)
 _CMD_LAUNCH_CVD_VNC_ARG = " -start_vnc_server=true"
+_CMD_LAUNCH_CVD_SUPER_IMAGE_ARG = " -super_image=%s"
 
 # In accordance with the number of network interfaces in
 # /etc/init.d/cuttlefish-common
@@ -81,10 +102,13 @@
                      "Enter 'y' to terminate current instance and launch a new "
                      "instance, enter anything else to exit out[y/N]: ")
 
-# The collection of image folder and CVD host package folder for local
-# instances.
+# The first two fields of this named tuple are image folder and CVD host
+# package folder which are essential for local instances. The following fields
+# are optional. They are set when the AVD spec requires to mix images.
 ArtifactPaths = collections.namedtuple(
-    "ArtifactPaths", ["image_dir", "host_bins"])
+    "ArtifactPaths",
+    ["image_dir", "host_bins", "misc_info", "ota_tools_dir", "system_image"],
+    defaults=[None, None, None])
 
 
 class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
@@ -181,6 +205,10 @@
 
         cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
         create_common.PrepareLocalInstanceDir(cvd_home_dir, avd_spec)
+        super_image_path = None
+        if artifact_paths.system_image:
+            super_image_path = self._MixSuperImage(cvd_home_dir,
+                                                   artifact_paths)
         runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
         # TODO(b/168171781): cvd_status of list/delete via the symbolic.
         self.PrepareLocalCvdToolsLink(cvd_home_dir, artifact_paths.host_bins)
@@ -194,6 +222,7 @@
                                        avd_spec.connect_webrtc,
                                        avd_spec.connect_vnc,
                                        avd_spec.gpu,
+                                       super_image_path,
                                        avd_spec.cfg.launch_args)
 
         result_report = report.Report(command="create")
@@ -251,6 +280,54 @@
             "CVD host binaries are not found. Please run `make hosttar`, or "
             "set --local-tool to an extracted CVD host package.")
 
+    @staticmethod
+    def _FindMiscInfo(image_dir):
+        """Find misc info in build output dir or extracted target files.
+
+        Args:
+            image_dir: The directory to search for misc info.
+
+        Returns:
+            image_dir if the directory structure looks like an output directory
+            in build environment.
+            image_dir/META if it looks like extracted target files.
+
+        Raises:
+            errors.CheckPathError if this method cannot find misc info.
+        """
+        misc_info_path = os.path.join(image_dir, _MISC_INFO_FILE_NAME)
+        if os.path.isfile(misc_info_path):
+            return misc_info_path
+        misc_info_path = os.path.join(image_dir, _TARGET_FILES_META_DIR_NAME,
+                                      _MISC_INFO_FILE_NAME)
+        if os.path.isfile(misc_info_path):
+            return misc_info_path
+        raise errors.CheckPathError(
+            "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir))
+
+    @staticmethod
+    def _FindImageDir(image_dir):
+        """Find images in build output dir or extracted target files.
+
+        Args:
+            image_dir: The directory to search for images.
+
+        Returns:
+            image_dir if the directory structure looks like an output directory
+            in build environment.
+            image_dir/IMAGES if it looks like extracted target files.
+
+        Raises:
+            errors.GetLocalImageError if this method cannot find images.
+        """
+        if glob.glob(os.path.join(image_dir, "*.img")):
+            return image_dir
+        subdir = os.path.join(image_dir, _TARGET_FILES_IMAGES_DIR_NAME)
+        if glob.glob(os.path.join(subdir, "*.img")):
+            return subdir
+        raise errors.GetLocalImageError(
+            "Cannot find images in %s." % image_dir)
+
     def GetImageArtifactsPath(self, avd_spec):
         """Get image artifacts path.
 
@@ -265,15 +342,57 @@
         Returns:
             ArtifactPaths object consisting of image directory and host bins
             package.
+
+        Raises:
+            errors.GetCvdLocalHostPackageError, errors.GetLocalImageError, or
+            errors.CheckPathError if any artifact is not found.
         """
-        return ArtifactPaths(
-            avd_spec.local_image_dir,
-            self._FindCvdHostBinaries(avd_spec.local_tool_dirs))
+        image_dir = os.path.abspath(avd_spec.local_image_dir)
+        host_bins_path = self._FindCvdHostBinaries(avd_spec.local_tool_dirs)
+
+        if not avd_spec.local_system_image_dir:
+            return ArtifactPaths(image_dir, host_bins_path)
+
+        misc_info_path = self._FindMiscInfo(image_dir)
+        image_dir = self._FindImageDir(image_dir)
+        ota_tools_dir = os.path.abspath(
+            ota_tools.FindOtaTools(avd_spec.local_tool_dirs))
+        system_image_path = os.path.abspath(
+            os.path.join(avd_spec.local_system_image_dir, _SYSTEM_IMAGE_NAME))
+        if not os.path.isfile(system_image_path):
+            raise errors.GetLocalImageError(
+                "Cannot find %s." % system_image_path)
+
+        return ArtifactPaths(image_dir, host_bins_path,
+                             misc_info=misc_info_path,
+                             ota_tools_dir=ota_tools_dir,
+                             system_image=system_image_path)
+
+    @staticmethod
+    def _MixSuperImage(output_dir, artifact_paths):
+        """Mix cuttlefish images and a system image into a super image.
+
+        Args:
+            output_dir: The path to the output directory.
+            artifact_paths: ArtifactPaths object.
+
+        Returns:
+            The path to the super image in output_dir.
+        """
+        ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir)
+        super_image_path = os.path.join(output_dir, _MIXED_SUPER_IMAGE_NAME)
+        ota.BuildSuperImage(
+            super_image_path, artifact_paths.misc_info,
+            lambda partition: ota_tools.GetImageForPartition(
+                partition, artifact_paths.image_dir,
+                system=artifact_paths.system_image))
+        return super_image_path
 
     @staticmethod
     def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, connect_adb,
-                            system_image_dir, runtime_dir, connect_webrtc,
-                            connect_vnc, gpu, launch_args):
+                            image_dir, runtime_dir, connect_webrtc,
+                            connect_vnc, gpu, super_image_path,
+                            launch_args):
         """Prepare launch_cvd command.
 
         Create the launch_cvd commands with all the required args and add
@@ -282,13 +401,14 @@
         Args:
             launch_cvd_path: String of launch_cvd path.
             hw_property: dict object of hw property.
-            system_image_dir: String of local images path.
+            image_dir: String of local images path.
             connect_adb: Boolean flag that enables adb_connector.
             runtime_dir: String of runtime directory path.
             connect_webrtc: Boolean of connect_webrtc.
             connect_vnc: Boolean of connect_vnc.
             gpu: String of gpu name, the gpu name of local instance should be
                  "default" if gpu is enabled.
+            super_image_path: String of non-default super image path.
             launch_args: String of launch args.
 
         Returns:
@@ -297,8 +417,7 @@
         launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
             hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
             hw_property["dpi"], hw_property["memory"],
-            ("true" if connect_adb else "false"), system_image_dir,
-            runtime_dir)
+            ("true" if connect_adb else "false"), image_dir, runtime_dir)
         if constants.HW_ALIAS_DISK in hw_property:
             launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS %
                                  hw_property[constants.HW_ALIAS_DISK])
@@ -311,6 +430,11 @@
         if gpu:
             launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_GPU_ARG
 
+        if super_image_path:
+            launch_cvd_w_args = (launch_cvd_w_args +
+                                 _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG %
+                                 super_image_path)
+
         if launch_args:
             launch_cvd_w_args = launch_cvd_w_args + " " + launch_args
 
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index 8a2ac9e..ab7d103 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -17,6 +17,7 @@
 
 import os
 import subprocess
+import tempfile
 import unittest
 import mock
 
@@ -51,6 +52,12 @@
 sg group2
 launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -guest_enforce_security=false -vm_manager=crosvm -start_webrtc=true -webrtc_public_ip=0.0.0.0
 EOF"""
+
+    LAUNCH_CVD_CMD_WITH_SUPER_IMAGE = """sg group1 <<EOF
+sg group2
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -start_vnc_server=true -super_image=fake_super_image
+EOF"""
+
     LAUNCH_CVD_CMD_WITH_ARGS = """sg group1 <<EOF
 sg group2
 launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -start_vnc_server=true -setupwizard_mode=REQUIRED
@@ -151,6 +158,7 @@
         mock_lock.LockIfNotInUse.assert_not_called()
 
     @mock.patch("acloud.create.local_image_local_instance.utils")
+    @mock.patch("acloud.create.local_image_local_instance.ota_tools")
     @mock.patch("acloud.create.local_image_local_instance.create_common")
     @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
                        "_LaunchCvd")
@@ -158,14 +166,18 @@
                        "PrepareLaunchCVDCmd")
     @mock.patch.object(instance, "GetLocalInstanceRuntimeDir")
     @mock.patch.object(instance, "GetLocalInstanceHomeDir")
-    def testCreateInstance(self, _mock_home_dir, _mock_runtime_dir,
+    def testCreateInstance(self, mock_home_dir, _mock_runtime_dir,
                            _mock_prepare_cmd, mock_launch_cvd,
-                           _mock_create_common, _mock_utils):
+                           _mock_create_common, mock_ota_tools, _mock_utils):
         """Test the report returned by _CreateInstance."""
         self.Patch(instance, "GetLocalInstanceName",
                    return_value="local-instance-1")
+        mock_home_dir.return_value = "/local-instance-1"
         artifact_paths = local_image_local_instance.ArtifactPaths(
-            "/image/path", "/host/bin/path")
+            "/image/path", "/host/bin/path", "/misc/info/path",
+            "/ota/tools/dir", "/system/image/path")
+        mock_ota_tools_object = mock.Mock()
+        mock_ota_tools.OtaTools.return_value = mock_ota_tools_object
         mock_avd_spec = mock.Mock(unlock_screen=False)
         local_ins = mock.Mock(
             adb_port=6520,
@@ -184,6 +196,9 @@
 
         self.assertEqual(report.data.get("devices"),
                          self._EXPECTED_DEVICES_IN_REPORT)
+        mock_ota_tools.OtaTools.assert_called_with("/ota/tools/dir")
+        mock_ota_tools_object.BuildSuperImage.assert_called_with(
+            "/local-instance-1/mixed_super.img", "/misc/info/path", mock.ANY)
 
         # Failure
         mock_launch_cvd.side_effect = errors.LaunchCVDFail("unit test")
@@ -224,6 +239,94 @@
                 [cvd_host_dir])
             self.assertEqual(path, cvd_host_dir)
 
+    @staticmethod
+    def _CreateEmptyFile(path):
+        os.makedirs(os.path.dirname(path), exist_ok=True)
+        with open(path, "w"):
+            pass
+
+    @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+    def testGetImageArtifactsPath(self, mock_ota_tools):
+        """Test GetImageArtifactsPath without system image dir."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            image_dir = "/unit/test"
+            cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+            self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+
+            mock_avd_spec = mock.Mock(
+                local_image_dir=image_dir,
+                local_system_image_dir=None,
+                local_tool_dirs=[cvd_dir])
+
+            paths = self.local_image_local_instance.GetImageArtifactsPath(
+                mock_avd_spec)
+
+        mock_ota_tools.FindOtaTools.assert_not_called()
+        self.assertEqual(paths, (image_dir, cvd_dir, None, None, None))
+
+    @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+    def testGetImageFromBuildEnvironment(self, mock_ota_tools):
+        """Test GetImageArtifactsPath with files in build environment."""
+        with tempfile.TemporaryDirectory() as temp_dir:
+            image_dir = os.path.join(temp_dir, "image")
+            cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+            mock_ota_tools.FindOtaTools.return_value = cvd_dir
+            system_image_dir = os.path.join(temp_dir, "system_image")
+            system_image_path = os.path.join(system_image_dir, "system.img")
+            misc_info_path = os.path.join(image_dir, "misc_info.txt")
+            self._CreateEmptyFile(os.path.join(image_dir, "boot.img"))
+            self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+            self._CreateEmptyFile(system_image_path)
+            self._CreateEmptyFile(misc_info_path)
+
+            mock_avd_spec = mock.Mock(
+                local_image_dir=image_dir,
+                local_system_image_dir=system_image_dir,
+                local_tool_dirs=[])
+
+            with mock.patch.dict("acloud.create.local_image_local_instance."
+                                 "os.environ",
+                                 {"ANDROID_SOONG_HOST_OUT": cvd_dir},
+                                 clear=True):
+                paths = self.local_image_local_instance.GetImageArtifactsPath(
+                    mock_avd_spec)
+
+        mock_ota_tools.FindOtaTools.assert_called_once()
+        self.assertEqual(paths,
+                         (image_dir, cvd_dir, misc_info_path, cvd_dir,
+                          system_image_path))
+
+    @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+    def testGetImageFromTargetFiles(self, mock_ota_tools):
+        """Test GetImageArtifactsPath with extracted target files."""
+        ota_tools_dir = "/mock_ota_tools"
+        mock_ota_tools.FindOtaTools.return_value = ota_tools_dir
+
+        with tempfile.TemporaryDirectory() as temp_dir:
+            image_dir = os.path.join(temp_dir, "image")
+            cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+            system_image_dir = os.path.join(temp_dir, "system_image")
+            system_image_path = os.path.join(system_image_dir, "system.img")
+            misc_info_path = os.path.join(image_dir, "META", "misc_info.txt")
+            self._CreateEmptyFile(os.path.join(image_dir, "IMAGES",
+                                               "boot.img"))
+            self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+            self._CreateEmptyFile(system_image_path)
+            self._CreateEmptyFile(misc_info_path)
+
+            mock_avd_spec = mock.Mock(
+                local_image_dir=image_dir,
+                local_system_image_dir=system_image_dir,
+                local_tool_dirs=[ota_tools_dir, cvd_dir])
+
+            paths = self.local_image_local_instance.GetImageArtifactsPath(
+                mock_avd_spec)
+
+        mock_ota_tools.FindOtaTools.assert_called_once()
+        self.assertEqual(paths,
+                         (os.path.join(image_dir, "IMAGES"), cvd_dir,
+                          misc_info_path, ota_tools_dir, system_image_path))
+
     @mock.patch.object(utils, "CheckUserInGroups")
     def testPrepareLaunchCVDCmd(self, mock_usergroups):
         """test PrepareLaunchCVDCmd."""
@@ -234,7 +337,7 @@
 
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
             constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
-            "fake_cvd_dir", False, True, None, None)
+            "fake_cvd_dir", False, True, None, None, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_DISK)
 
         # "disk" doesn't exist in hw_property.
@@ -242,24 +345,30 @@
                        "dpi": "fake", "memory": "fake"}
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
             constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
-            "fake_cvd_dir", False, True, None, None)
+            "fake_cvd_dir", False, True, None, None, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK)
 
         # "gpu" is enabled with "default"
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
             constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
-            "fake_cvd_dir", False, True, "default", None)
+            "fake_cvd_dir", False, True, "default", None, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK_WITH_GPU)
 
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
             constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
-            "fake_cvd_dir", True, False, None, None)
+            "fake_cvd_dir", True, False, None, None, None)
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_WEBRTC)
 
+        launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+            constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+            "fake_cvd_dir", False, True, None, "fake_super_image", None)
+        self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_SUPER_IMAGE)
+
         # Add args into launch command with "-setupwizard_mode=REQUIRED"
         launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
             constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
-            "fake_cvd_dir", False, True, None, "-setupwizard_mode=REQUIRED")
+            "fake_cvd_dir", False, True, None, None,
+            "-setupwizard_mode=REQUIRED")
         self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_ARGS)
 
     @mock.patch.object(utils, "GetUserAnswerYes")
diff --git a/create/remote_image_local_instance.py b/create/remote_image_local_instance.py
index caaf27b..5166efa 100644
--- a/create/remote_image_local_instance.py
+++ b/create/remote_image_local_instance.py
@@ -184,4 +184,6 @@
             raise errors.GetCvdLocalHostPackageError(
                 "No launch_cvd found. Please check downloaded artifacts dir: %s"
                 % image_dir)
+        # This method does not set the optional fields because launch_cvd loads
+        # the paths from the fetcher config in image_dir.
         return local_image_local_instance.ArtifactPaths(image_dir, image_dir)
diff --git a/errors.py b/errors.py
index fc6a084..5b6d249 100644
--- a/errors.py
+++ b/errors.py
@@ -41,7 +41,7 @@
 
     def __init__(self, code, message):
         self.code = code
-        super(HttpError, self).__init__(message)
+        super().__init__(message)
 
     @staticmethod
     def CreateFromHttpError(http_error):
@@ -83,6 +83,10 @@
     """To catch device boot errors."""
 
 
+class DownloadArtifactError(DriverError):
+    """To catch download artifact errors."""
+
+
 class NoSubnetwork(DriverError):
     """When there is no subnetwork for the GCE."""
 
diff --git a/internal/lib/auth.py b/internal/lib/auth.py
index 1d1cc09..ad03d6b 100644
--- a/internal/lib/auth.py
+++ b/internal/lib/auth.py
@@ -86,14 +86,20 @@
             " error message: %s" % (private_key_path, str(e)))
     return credentials
 
+
 # pylint: disable=invalid-name
-def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes):
+def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes,
+                                               creds_cache_file, user_agent):
     """Create credentials with a normal service account from json key file.
 
     Args:
         json_private_key_path: Path to the service account json key file.
         scopes: string, multiple scopes should be saperated by space.
                         Api scopes to request for the oauth token.
+        creds_cache_file: String, file name for the credential cache.
+                          e.g. .acloud_oauth2.dat
+                          Will be created at home folder.
+        user_agent: String, the user agent for the credential, e.g. "acloud"
 
     Returns:
         An oauth2client.OAuth2Credentials instance.
@@ -102,16 +108,22 @@
         errors.AuthenticationError: if failed to authenticate.
     """
     try:
-        return (
-            oauth2_service_account.ServiceAccountCredentials
-            .from_json_keyfile_name(
-                json_private_key_path, scopes=scopes))
+        credentials = oauth2_service_account.ServiceAccountCredentials.from_json_keyfile_name(
+            json_private_key_path, scopes=scopes)
+        storage = multistore_file.get_credential_storage(
+            filename=os.path.abspath(creds_cache_file),
+            client_id=credentials.client_id,
+            user_agent=user_agent,
+            scope=scopes)
+        credentials.set_store(storage)
     except EnvironmentError as e:
         raise errors.AuthenticationError(
             "Could not authenticate using json private key file (%s) "
             " error message: %s" % (json_private_key_path, str(e)))
 
-# pylint: disable=old-style-class
+    return credentials
+
+
 class RunFlowFlags():
     """Flags for oauth2client.tools.run_flow."""
 
@@ -199,21 +211,24 @@
     Returns:
         An oauth2client.OAuth2Credentials instance.
     """
+    if os.path.isabs(acloud_config.creds_cache_file):
+        creds_cache_file = acloud_config.creds_cache_file
+    else:
+        creds_cache_file = os.path.join(HOME_FOLDER,
+                                        acloud_config.creds_cache_file)
+
     if acloud_config.service_account_json_private_key_path:
         return _CreateOauthServiceAccountCredsWithJsonKey(
             acloud_config.service_account_json_private_key_path,
-            scopes=scopes)
+            scopes=scopes,
+            creds_cache_file=creds_cache_file,
+            user_agent=acloud_config.user_agent)
     if acloud_config.service_account_private_key_path:
         return _CreateOauthServiceAccountCreds(
             acloud_config.service_account_name,
             acloud_config.service_account_private_key_path,
             scopes=scopes)
 
-    if os.path.isabs(acloud_config.creds_cache_file):
-        creds_cache_file = acloud_config.creds_cache_file
-    else:
-        creds_cache_file = os.path.join(HOME_FOLDER,
-                                        acloud_config.creds_cache_file)
     return _CreateOauthUserCreds(
         creds_cache_file=creds_cache_file,
         client_id=acloud_config.client_id,
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index e42b29e..3a79bac 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -191,10 +191,10 @@
             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)
+        # Record the build info into metadata.
+        self._RecordBuildInfo(avd_spec, build_id, build_target,
+                              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)
@@ -236,24 +236,35 @@
             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.
+    def _RecordBuildInfo(self, avd_spec, build_id, build_target,
+                         system_build_id, system_build_target,
+                         kernel_build_id, kernel_build_target):
+        """Rocord the build information into metadata.
+
+        The build information includes build id and build target of base image,
+        system image, and kernel image.
 
         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.
+            build_id: String, build id for the base image.
+            build_target: String, target name for the base image,
+                          e.g. "cf_x86_phone-userdebug"
+            system_build_id: String, build id for the system image.
+            system_build_target: String, system build target name,
+                                 e.g. "cf_x86_phone-userdebug"
+            kernel_build_id: String, kernel build id, e.g. "223051", "P280427"
+            kernel_build_target: String, kernel build target name.
         """
         if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
+            build_id = avd_spec.remote_image.get(constants.BUILD_ID)
+            build_target = avd_spec.remote_image.get(constants.BUILD_TARGET)
             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 build_id and build_target:
+            self._metadata.update({"build_id": build_id})
+            self._metadata.update({"build_target": 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})
@@ -469,6 +480,9 @@
                 avd_spec.hw_property[constants.HW_X_RES],
                 avd_spec.hw_property[constants.HW_Y_RES],
                 avd_spec.hw_property[constants.HW_ALIAS_DPI]))
+            if avd_spec.gce_metadata:
+                for key, value in avd_spec.gce_metadata.items():
+                    metadata[key] = value
 
         disk_args = self._GetDiskArgs(
             instance, image_name, image_project, boot_disk_size_gb)
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index 3077945..34ea323 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -159,7 +159,7 @@
         created_subprocess.returncode = 0
         created_subprocess.communicate = mock.MagicMock(return_value=('', ''))
         self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
-                   "_RecordSystemAndKernelInfo")
+                   "_RecordBuildInfo")
         self.Patch(subprocess, "Popen", return_value=created_subprocess)
         self.Patch(subprocess, "check_call")
         self.Patch(os, "chmod")
@@ -219,35 +219,40 @@
             gpu=self.GPU,
             tags=None)
 
-    def testRecordSystemAndKernelInfo(self):
-        """Test RecordSystemAndKernelInfo"""
+    def testRecordBuildInfo(self):
+        """Test RecordBuildInfo"""
+        build_id = "build_id"
+        build_target = "build_target"
         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.image_source = constants.IMAGE_SRC_REMOTE
+        fake_avd_spec.remote_image = {constants.BUILD_ID: build_id,
+                                      constants.BUILD_TARGET: build_target}
         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({"build_id": build_id})
+        expected_metadata.update({"build_target": build_target})
         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.cvd_compute_client_multi_stage._RecordBuildInfo(
+            fake_avd_spec, build_id=None, build_target=None, 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,
+        # Test record metadata with build info for acloud create_cf
+        self.cvd_compute_client_multi_stage._RecordBuildInfo(
+            None, build_id, build_target, system_build_id, system_build_target,
             kernel_build_id, kernel_build_target)
         self.assertEqual(self.cvd_compute_client_multi_stage._metadata, expected_metadata)
 
diff --git a/internal/lib/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index d868403..33de884 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -107,15 +107,16 @@
             instance: String
 
         Raises:
-            Raises an errors.DeviceBootError exception if a failure is detected.
+            errors.DownloadArtifactError: If it fails to download artifact.
+            errors.DeviceBootError: If it fails to boot up.
         """
         if self.BOOT_FAILED_MSG in serial_out:
             if self.EMULATOR_FETCH_FAILED_MSG in serial_out:
-                raise errors.DeviceBootError(
+                raise errors.DownloadArtifactError(
                     "Failed to download emulator build. Re-run with a newer build."
                 )
             if self.ANDROID_FETCH_FAILED_MSG in serial_out:
-                raise errors.DeviceBootError(
+                raise errors.DownloadArtifactError(
                     "Failed to download system image build. Re-run with a newer build."
                 )
             if self.BOOT_TIMEOUT_MSG in serial_out:
@@ -188,6 +189,7 @@
                  ["http-server", "https-server"]
             launch_args: String of args for launch command.
         """
+        self._VerifyZoneByQuota()
         self._CheckMachineSize()
 
         # Add space for possible data partition.
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index 90e4499..c627f6d 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -73,7 +73,7 @@
 
     def setUp(self):
         """Set up the test."""
-        super(GoldfishComputeClientTest, self).setUp()
+        super().setUp()
         self.Patch(goldfish_compute_client.GoldfishComputeClient,
                    "InitResourceHandle")
         self.goldfish_compute_client = (
@@ -94,6 +94,9 @@
             return_value=[{
                 "fake_arg": "fake_value"
             }])
+        self.Patch(goldfish_compute_client.GoldfishComputeClient,
+                   "_VerifyZoneByQuota",
+                   return_value=True)
 
     @mock.patch("getpass.getuser", return_value="fake_user")
     def testCreateInstance(self, _mock_user):
diff --git a/list/instance.py b/list/instance.py
index 6c7ed7f..2a864c5 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -52,6 +52,7 @@
 _CVD_STATUS_BIN = "cvd_status"
 _LOCAL_INSTANCE_NAME_FORMAT = "local-instance-%(id)d"
 _LOCAL_INSTANCE_NAME_PATTERN = re.compile(r"^local-instance-(?P<id>\d+)$")
+_ACLOUDWEB_INSTANCE_START_STRING = "cf-"
 _MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
 _NO_ANDROID_ENV = "android source not available"
 _RE_GROUP_ADB = "local_adb_port"
@@ -700,6 +701,9 @@
                 avd_type = value
             elif key == constants.INS_KEY_AVD_FLAVOR:
                 avd_flavor = value
+        # TODO(176884236): Insert avd information into metadata of instance.
+        if not avd_type and name.startswith(_ACLOUDWEB_INSTANCE_START_STRING):
+            avd_type = constants.TYPE_CF
 
         # Find ssl tunnel info.
         adb_port = None
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 31118f5..6f01906 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -131,6 +131,8 @@
 from acloud.public.actions import create_goldfish_action
 from acloud.pull import pull
 from acloud.pull import pull_args
+from acloud.restart import restart
+from acloud.restart import restart_args
 from acloud.setup import setup
 from acloud.setup import setup_args
 
@@ -169,6 +171,7 @@
         delete_args.CMD_DELETE,
         reconnect_args.CMD_RECONNECT,
         pull_args.CMD_PULL,
+        restart_args.CMD_RESTART,
     ])
     parser = argparse.ArgumentParser(
         description=__doc__,
@@ -243,6 +246,9 @@
     # Command "reconnect"
     subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
 
+    # Command "restart"
+    subparser_list.append(restart_args.GetRestartArgParser(subparsers))
+
     # Command "powerwash"
     subparser_list.append(powerwash_args.GetPowerwashArgParser(subparsers))
 
@@ -429,6 +435,8 @@
         list_instances.Run(args)
     elif args.which == reconnect_args.CMD_RECONNECT:
         reconnect.Run(args)
+    elif args.which == restart_args.CMD_RESTART:
+        report = restart.Run(args)
     elif args.which == powerwash_args.CMD_POWERWASH:
         report = powerwash.Run(args)
     elif args.which == pull_args.CMD_PULL:
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 6f60dca..9c50646 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -32,14 +32,16 @@
 
 
 logger = logging.getLogger(__name__)
+_ACLOUD_BOOT_UP_ERROR = "ACLOUD_BOOT_UP_ERROR"
+_ACLOUD_DOWNLOAD_ARTIFACT_ERROR = "ACLOUD_DOWNLOAD_ARTIFACT_ERROR"
+# Error type of GCE quota error.
+_GCE_QUOTA_ERROR = "GCE_QUOTA_ERROR"
 _DICT_ERROR_TYPE = {
     constants.STAGE_INIT: "ACLOUD_INIT_ERROR",
     constants.STAGE_GCE: "ACLOUD_CREATE_GCE_ERROR",
-    constants.STAGE_ARTIFACT: "ACLOUD_DOWNLOAD_ARTIFACT_ERROR",
-    constants.STAGE_BOOT_UP: "ACLOUD_BOOT_UP_ERROR",
+    constants.STAGE_ARTIFACT: _ACLOUD_DOWNLOAD_ARTIFACT_ERROR,
+    constants.STAGE_BOOT_UP: _ACLOUD_BOOT_UP_ERROR,
 }
-# Error type of GCE quota error.
-_GCE_QUOTA_ERROR = "GCE_QUOTA_ERROR"
 
 
 def CreateSshKeyPairIfNecessary(cfg):
@@ -274,7 +276,9 @@
                     ssh_user=constants.GCE_USER,
                     extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
             if device.instance_name in failures:
-                reporter.SetErrorType(_DICT_ERROR_TYPE[device.stage])
+                reporter.SetErrorType(_ACLOUD_BOOT_UP_ERROR)
+                if device.stage:
+                    reporter.SetErrorType(_DICT_ERROR_TYPE[device.stage])
                 reporter.AddData(key="devices_failing_boot", value=device_dict)
                 reporter.AddError(str(failures[device.instance_name]))
             else:
@@ -282,6 +286,8 @@
     except (errors.DriverError, errors.CheckGCEZonesQuotaError) as e:
         if isinstance(e, errors.CheckGCEZonesQuotaError):
             reporter.SetErrorType(_GCE_QUOTA_ERROR)
+        if isinstance(e, errors.DownloadArtifactError):
+            reporter.SetErrorType(_ACLOUD_DOWNLOAD_ARTIFACT_ERROR)
         reporter.AddError(str(e))
         reporter.SetStatus(report.Status.FAIL)
     return reporter
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 786a49f..cfd36aa 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -31,8 +31,10 @@
 
 
 logger = logging.getLogger(__name__)
-# bootloader is one file required to launch AVD.
+# bootloader and kernel are files required to launch AVD.
 _BOOTLOADER = "bootloader"
+_KERNEL = "kernel"
+_ARTIFACT_FILES = ["*.img", _BOOTLOADER, _KERNEL]
 _HOME_FOLDER = os.path.expanduser("~")
 
 
@@ -52,7 +54,7 @@
     """
     def __init__(self, avd_spec, local_image_artifact=None,
                  cvd_host_package_artifact=None):
-        super(RemoteInstanceDeviceFactory, self).__init__(avd_spec, local_image_artifact)
+        super().__init__(avd_spec, local_image_artifact)
         self._cvd_host_package_artifact = cvd_host_package_artifact
 
     # pylint: disable=broad-except
@@ -279,10 +281,11 @@
             except IOError:
                 # Older builds may not have a required_images file. In this case
                 # we fall back to *.img.
-                artifact_files = [
-                    os.path.basename(image) for image in
-                    glob.glob(os.path.join(images_dir, "*.img"))]
-                artifact_files.append(_BOOTLOADER)
+                artifact_files = []
+                for file_name in _ARTIFACT_FILES:
+                    artifact_files.extend(
+                        os.path.basename(image) for image in glob.glob(
+                            os.path.join(images_dir, file_name)))
             cmd = ("tar -cf - --lzop -S -C {images_dir} {artifact_files} | "
                    "{ssh_cmd} -- tar -xf - --lzop -S".format(
                        images_dir=images_dir,
diff --git a/public/actions/remote_instance_cf_device_factory_test.py b/public/actions/remote_instance_cf_device_factory_test.py
index 12a9e30..34dd875 100644
--- a/public/actions/remote_instance_cf_device_factory_test.py
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -288,10 +288,10 @@
 
         # Test local image get from local folder case.
         fake_image = None
-        self.Patch(glob, "glob", return_value=["fake.img"])
+        self.Patch(glob, "glob", side_effect=[["fake.img"], ["bootloader"], ["kernel"]])
         factory._UploadArtifacts(fake_image, fake_host_package, fake_local_image_dir)
         expected_cmd = (
-            "tar -cf - --lzop -S -C %s fake.img bootloader | "
+            "tar -cf - --lzop -S -C %s fake.img bootloader kernel | "
             "%s -- tar -xf - --lzop -S" %
             (fake_local_image_dir, factory._ssh.GetBaseCmd(constants.SSH_BIN)))
         mock_shell.assert_called_once_with(expected_cmd)
diff --git a/public/config.py b/public/config.py
index 85d7d43..02ab4f0 100755
--- a/public/config.py
+++ b/public/config.py
@@ -269,9 +269,6 @@
                 parsed_args.service_account_json_private_key_path)
         if parsed_args.which == "create_gf" and parsed_args.base_image:
             self.stable_goldfish_host_image_name = parsed_args.base_image
-        if parsed_args.which == create_args.CMD_CREATE and not self.hw_property:
-            flavor = parsed_args.flavor or constants.FLAVOR_PHONE
-            self.hw_property = self.common_hw_property_map.get(flavor, "")
         if parsed_args.which in [create_args.CMD_CREATE, "create_cf"]:
             if parsed_args.network:
                 self.network = parsed_args.network
diff --git a/public/config_test.py b/public/config_test.py
index cbe7a4b..be57870 100644
--- a/public/config_test.py
+++ b/public/config_test.py
@@ -276,25 +276,8 @@
             config.AcloudConfigManager.LoadConfigFromProtocolBuffer(
                 self.config_file, internal_config_pb2.InternalConfig)
 
-    def testOverrideWithHWProperty(self):
-        """Test override hw property by flavor type."""
-        # test override with an exist flavor.
-        self.cfg.hw_property = None
-        args = mock.MagicMock()
-        args.flavor = "phone"
-        args.which = "create"
-        self.cfg.OverrideWithArgs(args)
-        self.assertEqual(self.cfg.hw_property,
-                         "cpu:2,resolution:1080x1920,dpi:420,memory:4g,disk:8g")
-
-        # test override with a nonexistent flavor.
-        self.cfg.hw_property = None
-        args = mock.MagicMock()
-        args.flavor = "non-exist-flavor"
-        args.which = "create"
-        self.cfg.OverrideWithArgs(args)
-        self.assertEqual(self.cfg.hw_property, "")
-
+    def testOverrideWithArgs(self):
+        """Test OverrideWithArgs."""
         # test override zone.
         self.cfg.zone = "us-central1-f"
         args = mock.MagicMock()
diff --git a/restart/__init__.py b/restart/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/restart/__init__.py
diff --git a/restart/restart.py b/restart/restart.py
new file mode 100644
index 0000000..43253d5
--- /dev/null
+++ b/restart/restart.py
@@ -0,0 +1,62 @@
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""Restart entry point.
+
+This command will restart the CF AVD from a remote instance.
+"""
+
+from __future__ import print_function
+
+from acloud import errors
+from acloud.list import list as list_instances
+from acloud.public import config
+from acloud.public import report
+
+
+def RestartFromInstance(instance, instance_id):
+    """Restart AVD from remote CF instance.
+
+    Args:
+        instance: list.Instance() object.
+        instance_id: Integer of the instance id.
+
+    Returns:
+        A Report instance.
+    """
+    # TODO(162382338): rewrite this function to restart AVD from the remote instance.
+    print("We will restart AVD id (%s) from the instance: %s."
+          % (instance_id, instance.name))
+    return report.Report(command="restart")
+
+
+def Run(args):
+    """Run restart.
+
+    After restart command executed, tool will return one Report instance.
+
+    Args:
+        args: Namespace object from argparse.parse_args.
+
+    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 RestartFromInstance(instance[0], args.instance_id)
+    raise errors.CommandArgError("Please assign the '--instance-name' in your command.")
diff --git a/restart/restart_args.py b/restart/restart_args.py
new file mode 100644
index 0000000..bc114d3
--- /dev/null
+++ b/restart/restart_args.py
@@ -0,0 +1,59 @@
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""Restart args.
+
+Defines the restart arg parser that holds restart specific args.
+"""
+import argparse
+
+
+CMD_RESTART = "restart"
+
+
+def GetRestartArgParser(subparser):
+    """Return the restart arg parser.
+
+    Args:
+       subparser: argparse.ArgumentParser that is attached to main acloud cmd.
+
+    Returns:
+        argparse.ArgumentParser with restart options defined.
+    """
+    restart_parser = subparser.add_parser(CMD_RESTART)
+    restart_parser.required = False
+    restart_parser.set_defaults(which=CMD_RESTART)
+    restart_group = restart_parser.add_mutually_exclusive_group()
+    restart_group.add_argument(
+        "--instance-name",
+        dest="instance_name",
+        type=str,
+        required=False,
+        help="The name of the remote instance that need to restart the AVDs.")
+    # TODO(b/118439885): Old arg formats to support transition, delete when
+    # transistion is done.
+    restart_group.add_argument(
+        "--instance_name",
+        dest="instance_name",
+        type=str,
+        required=False,
+        help=argparse.SUPPRESS)
+    restart_parser.add_argument(
+        "--instance-id",
+        dest="instance_id",
+        type=int,
+        required=False,
+        default=1,
+        help="The instance id of the remote instance that need to be restart.")
+
+    return restart_parser