tremplin: Implement StopContainer RPC.

This allows us to stop individual containers on a VM without
taking down the whole VM.

BUG=chromium:1261319
TEST=Manual testing

Cq-Depend: chromium:3325825
Change-Id: I7133ad60a37c1488625242f558d6fa9db94bfc52
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/tremplin/+/3325709
Reviewed-by: David Munro <davidmunro@google.com>
Tested-by: Nicholas Verne <nverne@chromium.org>
Commit-Queue: Nicholas Verne <nverne@chromium.org>
diff --git a/src/chromiumos/tremplin/tremplin.go b/src/chromiumos/tremplin/tremplin.go
index 6021655..2568d99 100644
--- a/src/chromiumos/tremplin/tremplin.go
+++ b/src/chromiumos/tremplin/tremplin.go
@@ -321,7 +321,7 @@
 }
 
 // stopContainer stops the specified container.
-func (s *tremplinServer) stopContainer(containerName string) error {
+func (s *tremplinServer) stopContainer(containerName string, wait bool) (lxd.Operation, error) {
 	reqState := api.ContainerStatePut{
 		Action:  "stop",
 		Timeout: -1,
@@ -330,12 +330,15 @@
 	log.Printf("stopContainer name: %v lxd: %v", containerName, s.lxd)
 	op, err := s.lxd.UpdateContainerState(containerName, reqState, "")
 	if err != nil {
-		return fmt.Errorf("error calling stop container %s: %v", containerName, err)
+		return nil, fmt.Errorf("error calling stop container %s: %v", containerName, err)
+	}
+	if !wait {
+		return op, nil
 	}
 	if err = op.Wait(); err != nil {
-		return fmt.Errorf("error waiting to stop container %s: %v", containerName, err)
+		return nil, fmt.Errorf("error waiting to stop container %s: %v", containerName, err)
 	}
-	return nil
+	return nil, nil
 }
 
 // writeContainerFile writes content to fileName in containerName.
@@ -463,7 +466,7 @@
 
 	// Stop <container>-crostini-remap.
 	log.Printf("Remap: stop %s", remapName)
-	err = s.stopContainer(remapName)
+	_, err = s.stopContainer(remapName, true)
 	if err != nil {
 		return nil, "", err
 	}
@@ -996,12 +999,7 @@
 	}
 
 	if container.StatusCode == api.Running {
-		reqState := api.ContainerStatePut{
-			Action:  "stop",
-			Timeout: -1,
-			Force:   true,
-		}
-		op, err := s.lxd.UpdateContainerState(container.Name, reqState, "")
+		op, err := s.stopContainer(container.Name, false)
 
 		if err != nil {
 			response.Status = pb.DeleteContainerResponse_FAILED
@@ -1029,6 +1027,70 @@
 	return response, nil
 }
 
+// StopContainer implements tremplin.StopContainer.
+func (s *tremplinServer) StopContainer(ctx context.Context, in *pb.StopContainerRequest) (*pb.StopContainerResponse, error) {
+	log.Printf("Received StopContainer RPC: %s", in.ContainerName)
+
+	response := &pb.StopContainerResponse{}
+
+	container, _, err := s.lxd.GetContainer(in.ContainerName)
+	if container == nil {
+		response.Status = pb.StopContainerResponse_DOES_NOT_EXIST
+		return response, nil
+	}
+	if err != nil {
+		response.Status = pb.StopContainerResponse_FAILED
+		response.FailureReason = fmt.Sprintf("failed to find container: %v", err)
+		return response, nil
+	}
+
+	sendStatus := func(op api.Operation) {
+		req := &pb.ContainerStopProgress{ContainerName: container.Name}
+		switch op.StatusCode {
+		case api.Pending, api.Running:
+			req.Status = pb.ContainerStopProgress_STOPPING
+		case api.Success:
+			req.Status = pb.ContainerStopProgress_STOPPED
+		case api.Cancelled:
+			req.Status = pb.ContainerStopProgress_CANCELLED
+			req.FailureReason = op.Err
+		case api.Failure:
+			req.Status = pb.ContainerStopProgress_FAILED
+			req.FailureReason = op.Err
+		default:
+			req.Status = pb.ContainerStopProgress_UNKNOWN
+			req.FailureReason = fmt.Sprintf("unhandled stop status: %s, %s", op.Status, op.Err)
+		}
+		if _, err := s.listenerClient.UpdateStopStatus(context.Background(), req); err != nil {
+			log.Printf("Could not update stop status on host: %v", err)
+		}
+
+	}
+
+	if container.StatusCode == api.Running {
+		op, err := s.stopContainer(container.Name, false)
+
+		if err != nil {
+			response.Status = pb.StopContainerResponse_FAILED
+			response.FailureReason = fmt.Sprintf("failed to stop container: %v", err)
+			return response, nil
+		}
+
+		_, err = op.AddHandler(sendStatus)
+
+		if err != nil {
+			response.Status = pb.StopContainerResponse_FAILED
+			response.FailureReason = fmt.Sprintf("failed to add stop operation handler: %v", err)
+			return response, nil
+		}
+		response.Status = pb.StopContainerResponse_STOPPING
+	} else {
+		response.Status = pb.StopContainerResponse_STOPPED
+	}
+
+	return response, nil
+}
+
 func (s *tremplinServer) handleStopOperation(containerName string, op api.Operation) {
 	req := &pb.ContainerDeletionProgress{
 		ContainerName: containerName,
@@ -1723,7 +1785,7 @@
 	}
 	if c.StatusCode != 0 && c.StatusCode != api.Stopped {
 		log.Printf("Force stopping container %s before deleting", containerName)
-		err := s.stopContainer(containerName)
+		_, err := s.stopContainer(containerName, true)
 		if err != nil {
 			return err
 		}
diff --git a/src/chromiumos/tremplin/upgrade_container.go b/src/chromiumos/tremplin/upgrade_container.go
index 64d9061..e2a60e8 100644
--- a/src/chromiumos/tremplin/upgrade_container.go
+++ b/src/chromiumos/tremplin/upgrade_container.go
@@ -19,7 +19,7 @@
 
 func (s *tremplinServer) shutDownContainer(containerName string) error {
 	log.Printf("shutdown")
-	err := s.stopContainer(containerName)
+	_, err := s.stopContainer(containerName, true)
 	if err != nil {
 		return fmt.Errorf("Failed to stop container %v with error %v", containerName, err)
 	}