Remote API v2 migration.

Major differences between v1test and v2 that are implemented here:
- Execute call streams Operation updates
- WaitExecution call replaces Watcher API
- Action is no longer part of the Execute request, and must be uploaded separately
- output spec and platform moved from Action to Command

Also, adding retries to operations lost on the server, resolving a TODO.

TESTED=unit tests, LRE, RBE.
TYPE_CHANGE_OK=this is a breaking change by design, will update proto parsing tool as well
TAG_CHANGE_OK=this way give us flexibility to update the parsing tool to support both versions
PiperOrigin-RevId: 208990450
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
index e99cc6e..65570b6 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
@@ -16,6 +16,14 @@
 
 import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture;
 
+import build.bazel.remote.execution.v2.Action;
+import build.bazel.remote.execution.v2.ActionResult;
+import build.bazel.remote.execution.v2.Command;
+import build.bazel.remote.execution.v2.Digest;
+import build.bazel.remote.execution.v2.ExecuteRequest;
+import build.bazel.remote.execution.v2.ExecuteResponse;
+import build.bazel.remote.execution.v2.LogFile;
+import build.bazel.remote.execution.v2.Platform;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -48,14 +56,6 @@
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.remoteexecution.v1test.Action;
-import com.google.devtools.remoteexecution.v1test.ActionResult;
-import com.google.devtools.remoteexecution.v1test.Command;
-import com.google.devtools.remoteexecution.v1test.Digest;
-import com.google.devtools.remoteexecution.v1test.ExecuteRequest;
-import com.google.devtools.remoteexecution.v1test.ExecuteResponse;
-import com.google.devtools.remoteexecution.v1test.LogFile;
-import com.google.devtools.remoteexecution.v1test.Platform;
 import com.google.protobuf.TextFormat;
 import com.google.protobuf.TextFormat.ParseException;
 import io.grpc.Context;
@@ -136,7 +136,7 @@
   public SpawnResult exec(Spawn spawn, SpawnExecutionContext context)
       throws ExecException, InterruptedException, IOException {
     if (!Spawns.mayBeExecutedRemotely(spawn) || remoteCache == null) {
-      return fallbackRunner.get().exec(spawn, context);
+      return execLocally(spawn, context);
     }
 
     context.report(ProgressStatus.EXECUTING, getName());
@@ -147,13 +147,15 @@
     TreeNode inputRoot = repository.buildFromActionInputs(inputMap);
     repository.computeMerkleDigests(inputRoot);
     maybeWriteParamFilesLocally(spawn);
-    Command command = buildCommand(spawn.getArguments(), spawn.getEnvironment());
+    Command command = buildCommand(
+        spawn.getOutputFiles(),
+        spawn.getArguments(),
+        spawn.getEnvironment(),
+        spawn.getExecutionPlatform());
     Action action =
         buildAction(
-            spawn.getOutputFiles(),
             digestUtil.compute(command),
             repository.getMerkleDigest(inputRoot),
-            spawn.getExecutionPlatform(),
             context.getTimeout(),
             Spawns.mayBeCached(spawn));
 
@@ -190,26 +192,27 @@
           }
         }
       } catch (IOException e) {
-        return execLocallyOrFail(spawn, context, inputMap, actionKey, uploadLocalResults, e);
+        return execLocallyAndUploadOrFail(
+            spawn, context, inputMap, actionKey, action, command, uploadLocalResults, e);
       }
 
       if (remoteExecutor == null) {
         // Remote execution is disabled and so execute the spawn on the local machine.
-        return execLocally(spawn, context, inputMap, uploadLocalResults, remoteCache, actionKey);
-      }
+        return execLocallyAndUpload(
+            spawn, context, inputMap, remoteCache, actionKey, action, command, uploadLocalResults);
+       }
 
       ExecuteRequest request =
           ExecuteRequest.newBuilder()
               .setInstanceName(remoteOptions.remoteInstanceName)
-              .setAction(action)
+              .setActionDigest(actionKey.getDigest())
               .setSkipCacheLookup(!acceptCachedResult)
               .build();
       try {
         return retrier.execute(
             () -> {
               // Upload the command and all the inputs into the remote cache.
-              remoteCache.ensureInputsPresent(repository, execRoot, inputRoot, command);
-
+             remoteCache.ensureInputsPresent(repository, execRoot, inputRoot, action, command);
               ExecuteResponse reply = remoteExecutor.executeRemotely(request);
               maybeDownloadServerLogs(reply, actionKey);
 
@@ -219,8 +222,10 @@
                   .build();
             });
       } catch (IOException e) {
-        return execLocallyOrFail(spawn, context, inputMap, actionKey, uploadLocalResults, e);
+        return execLocallyAndUploadOrFail(
+            spawn, context, inputMap, actionKey, action, command, uploadLocalResults, e);
       }
+
     } finally {
       withMetadata.detach(previous);
     }
@@ -258,7 +263,7 @@
           logPath = parent.getRelative(e.getKey());
           logCount++;
           try {
-            getFromFuture(remoteCache.downloadFile(logPath, e.getValue().getDigest(), null));
+            getFromFuture(remoteCache.downloadFile(logPath, e.getValue().getDigest()));
           } catch (IOException ex) {
             reportOnce(Event.warn("Failed downloading server logs from the remote cache."));
           }
@@ -280,11 +285,18 @@
         .setExitCode(exitCode);
   }
 
-  private SpawnResult execLocallyOrFail(
+  private SpawnResult execLocally(Spawn spawn, SpawnExecutionContext context)
+      throws ExecException, InterruptedException, IOException {
+    return fallbackRunner.get().exec(spawn, context);
+  }
+
+  private SpawnResult execLocallyAndUploadOrFail(
       Spawn spawn,
       SpawnExecutionContext context,
       SortedMap<PathFragment, ActionInput> inputMap,
       ActionKey actionKey,
+      Action action,
+      Command command,
       boolean uploadLocalResults,
       IOException cause)
       throws ExecException, InterruptedException, IOException {
@@ -296,7 +308,8 @@
     if (remoteOptions.remoteLocalFallback
         && !(cause instanceof RetryException
             && RemoteRetrierUtils.causedByExecTimeout((RetryException) cause))) {
-      return execLocally(spawn, context, inputMap, uploadLocalResults, remoteCache, actionKey);
+      return execLocallyAndUpload(
+          spawn, context, inputMap, remoteCache, actionKey, action, command, uploadLocalResults);
     }
     return handleError(cause, context.getFileOutErr(), actionKey);
   }
@@ -343,38 +356,14 @@
   }
 
   static Action buildAction(
-      Collection<? extends ActionInput> outputs,
       Digest command,
       Digest inputRoot,
-      @Nullable PlatformInfo executionPlatform,
       Duration timeout,
       boolean cacheable) {
 
     Action.Builder action = Action.newBuilder();
     action.setCommandDigest(command);
     action.setInputRootDigest(inputRoot);
-    ArrayList<String> outputPaths = new ArrayList<>();
-    ArrayList<String> outputDirectoryPaths = new ArrayList<>();
-    for (ActionInput output : outputs) {
-      String pathString = output.getExecPathString();
-      if (output instanceof Artifact && ((Artifact) output).isTreeArtifact()) {
-        outputDirectoryPaths.add(pathString);
-      } else {
-        outputPaths.add(pathString);
-      }
-    }
-    Collections.sort(outputPaths);
-    Collections.sort(outputDirectoryPaths);
-    action.addAllOutputFiles(outputPaths);
-    action.addAllOutputDirectories(outputDirectoryPaths);
-
-    // Get the remote platform properties.
-    if (executionPlatform != null) {
-      Platform platform =
-          parsePlatform(executionPlatform.label(), executionPlatform.remoteExecutionProperties());
-      action.setPlatform(platform);
-    }
-
     if (!timeout.isZero()) {
       action.setTimeout(com.google.protobuf.Duration.newBuilder().setSeconds(timeout.getSeconds()));
     }
@@ -399,8 +388,33 @@
     return platformBuilder.build();
   }
 
-  static Command buildCommand(List<String> arguments, ImmutableMap<String, String> env) {
+  static Command buildCommand(
+      Collection<? extends ActionInput> outputs,
+      List<String> arguments,
+      ImmutableMap<String, String> env,
+      @Nullable PlatformInfo executionPlatform) {
     Command.Builder command = Command.newBuilder();
+    ArrayList<String> outputFiles = new ArrayList<>();
+    ArrayList<String> outputDirectories = new ArrayList<>();
+    for (ActionInput output : outputs) {
+      String pathString = output.getExecPathString();
+      if (output instanceof Artifact && ((Artifact) output).isTreeArtifact()) {
+        outputDirectories.add(pathString);
+      } else {
+        outputFiles.add(pathString);
+      }
+    }
+    Collections.sort(outputFiles);
+    Collections.sort(outputDirectories);
+    command.addAllOutputFiles(outputFiles);
+    command.addAllOutputDirectories(outputDirectories);
+
+    // Get the remote platform properties.
+    if (executionPlatform != null) {
+      Platform platform =
+          parsePlatform(executionPlatform.label(), executionPlatform.remoteExecutionProperties());
+      command.setPlatform(platform);
+    }
     command.addAllArguments(arguments);
     // Sorting the environment pairs by variable name.
     TreeSet<String> variables = new TreeSet<>(env.keySet());
@@ -430,35 +444,19 @@
     return ctimes;
   }
 
-  /**
-   * Execute a {@link Spawn} locally, using {@link #fallbackRunner}.
-   *
-   * <p>If possible also upload the {@link SpawnResult} to a remote cache.
-   */
-  private SpawnResult execLocally(
-      Spawn spawn,
-      SpawnExecutionContext context,
-      SortedMap<PathFragment, ActionInput> inputMap,
-      boolean uploadToCache,
-      @Nullable AbstractRemoteActionCache remoteCache,
-      @Nullable ActionKey actionKey)
-      throws ExecException, IOException, InterruptedException {
-    if (uploadToCache && remoteCache != null && actionKey != null) {
-      return execLocallyAndUpload(spawn, context, inputMap, remoteCache, actionKey);
-    }
-    return fallbackRunner.get().exec(spawn, context);
-  }
-
   @VisibleForTesting
   SpawnResult execLocallyAndUpload(
       Spawn spawn,
       SpawnExecutionContext context,
       SortedMap<PathFragment, ActionInput> inputMap,
       AbstractRemoteActionCache remoteCache,
-      ActionKey actionKey)
+      ActionKey actionKey,
+      Action action,
+      Command command,
+      boolean uploadLocalResults)
       throws ExecException, IOException, InterruptedException {
     Map<Path, Long> ctimesBefore = getInputCtimes(inputMap);
-    SpawnResult result = fallbackRunner.get().exec(spawn, context);
+    SpawnResult result = execLocally(spawn, context);
     Map<Path, Long> ctimesAfter = getInputCtimes(inputMap);
     for (Map.Entry<Path, Long> e : ctimesBefore.entrySet()) {
       // Skip uploading to remote cache, because an input was modified during execution.
@@ -466,13 +464,17 @@
         return result;
       }
     }
+    if (!uploadLocalResults) {
+      return result;
+    }
     boolean uploadAction =
         Spawns.mayBeCached(spawn)
             && Status.SUCCESS.equals(result.status())
             && result.exitCode() == 0;
     Collection<Path> outputFiles = resolveActionInputs(execRoot, spawn.getOutputFiles());
     try {
-      remoteCache.upload(actionKey, execRoot, outputFiles, context.getFileOutErr(), uploadAction);
+      remoteCache.upload(
+          actionKey, action, command, execRoot, outputFiles, context.getFileOutErr(), uploadAction);
     } catch (IOException e) {
       if (verboseFailures) {
         report(Event.debug("Upload to remote cache failed: " + e.getMessage()));