cros_flash: Allow stateful payload not to be copied to device

Some devices have very limited size stateful partition. Currently, cros
flash is failing for such devices because cros flash copies the payloads
into the DUT first before updating the device. Hence, the lack of space
problem. This CL adds a new flag --no-copy-payloads-to-device that if
passed the stateful update payload is not copied to the device first,
rather it is piped to the 'tar' command that untars the stateful update.

This approach in theory should make cros flash faster because it
eliminates one disk write and one disk read. However, experiments shows
that this flag makes cros flash slower but about 10-15 seconds (for a
electro device). Probably, the reason could be how inefficient the piped
file is read by the tar command. Hence, we decided not to enable this
option by default.

BUG=b:170359436
TEST=cros flash --no-ping <dut-ip> latest/reef/test
TEST=cros flash --no-ping --no-copy-payloads-to-device <dut-ip> latest/reef/test

Change-Id: Iecfbf3460bb4c48918edf63045d8372d7170cfc0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2555531
Tested-by: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
Commit-Queue: Mike Frysinger <vapier@chromium.org>
Auto-Submit: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/cli/flash.py b/cli/flash.py
index 2a6071d..16c137c 100644
--- a/cli/flash.py
+++ b/cli/flash.py
@@ -363,7 +363,8 @@
                board=None, src_image_to_delta=None, wipe=True, debug=False,
                yes=False, force=False, ssh_private_key=None, ping=True,
                disable_verification=False, send_payload_in_parallel=False,
-               clear_tpm_owner=False, version=None):
+               clear_tpm_owner=False, version=None,
+               copy_payloads_to_device=True):
     """Initializes RemoteDeviceUpdater"""
     if not stateful_update and not rootfs_update:
       raise ValueError('No update operation to perform; either stateful or'
@@ -389,6 +390,7 @@
     self.force = force
     self.send_payload_in_parallel = send_payload_in_parallel
     self.version = version
+    self.copy_payloads_to_device = copy_payloads_to_device
 
   def Cleanup(self):
     """Cleans up the temporary directory."""
@@ -526,7 +528,8 @@
               yes=self.yes,
               send_payload_in_parallel=self.send_payload_in_parallel,
               resolve_app_id_mismatch=True,
-              transfer_class=auto_updater_transfer.LocalTransfer)
+              transfer_class=auto_updater_transfer.LocalTransfer,
+              copy_payloads_to_device=self.copy_payloads_to_device)
           chromeos_AU.RunUpdate()
 
         except Exception:
@@ -552,7 +555,7 @@
           reboot=True, wipe=True, ssh_private_key=None, ping=True,
           disable_rootfs_verification=False, clear_cache=False, yes=False,
           force=False, debug=False, send_payload_in_parallel=False,
-          clear_tpm_owner=False, version=None):
+          clear_tpm_owner=False, version=None, copy_payloads_to_device=True):
   """Flashes a device, USB drive, or file with an image.
 
   This provides functionality common to `cros flash` and `brillo flash`
@@ -584,6 +587,9 @@
     send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
         up transmissions for long haul between endpoints.
     version: Default version.
+    copy_payloads_to_device: If True, update payloads are copied to the
+        Chromium OS device first. Otherwise, they are piped through SSH.
+        Currently, this only applies to the stateful payloads.
 
   Raises:
     FlashError: An unrecoverable error occured.
@@ -630,7 +636,8 @@
         ping=ping,
         disable_verification=disable_rootfs_verification,
         send_payload_in_parallel=send_payload_in_parallel,
-        version=version)
+        version=version,
+        copy_payloads_to_device=copy_payloads_to_device)
     updater.Run()
   elif device.scheme == commandline.DEVICE_SCHEME_USB:
     path = osutils.ExpandPath(device.path) if device.path else ''