launch-multi2: split up logs from nodes into prefixed lines

Currently it's impossible to tell apart logs from each node.

In general, we should move this over the the debug logging API instead
of relying on qemu stdout, but this will do for now.

Test Plan: Shouldn't affect any tests, as we don't actually test multi-node setups. Truth be told, we should.

X-Origin-Diff: phab/D670
GitOrigin-RevId: 7b4e170e634096bc40432fbef0844d9924957182
diff --git a/metropolis/test/launch/cli/launch-multi2/main.go b/metropolis/test/launch/cli/launch-multi2/main.go
index 265d6a0..1596e60 100644
--- a/metropolis/test/launch/cli/launch-multi2/main.go
+++ b/metropolis/test/launch/cli/launch-multi2/main.go
@@ -18,6 +18,8 @@
 
 import (
 	"context"
+	"fmt"
+	"io"
 	"log"
 	"os"
 	"os/signal"
@@ -28,10 +30,32 @@
 	"google.golang.org/grpc"
 
 	common "git.monogon.dev/source/nexantic.git/metropolis/node"
+	"git.monogon.dev/source/nexantic.git/metropolis/node/common/logbuffer"
 	apb "git.monogon.dev/source/nexantic.git/metropolis/proto/api"
 	"git.monogon.dev/source/nexantic.git/metropolis/test/launch"
 )
 
+// prefixedStdout is a os.Stdout proxy that prefixes every line with a constant
+// prefix. This is used to show logs from two Metropolis nodes without getting
+// them confused.
+// TODO(q3k): move to logging API instead of relying on qemu stdout, and remove
+// this function.
+func prefixedStdout(prefix string) io.ReadWriter {
+	lb := logbuffer.NewLineBuffer(2048, func(l *logbuffer.Line) {
+		fmt.Fprintf(os.Stdout, "%s%s\n", prefix, l.Data)
+	})
+	// Make a ReaderWriter from LineBuffer (a Reader), by combining into an
+	// anonymous struct with a io.MultiReader() (which will always return EOF
+	// on every Read if given no underlying readers).
+	return struct {
+		io.Reader
+		io.Writer
+	}{
+		Reader: io.MultiReader(),
+		Writer: lb,
+	}
+}
+
 func main() {
 	sigs := make(chan os.Signal, 1)
 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@@ -50,7 +74,10 @@
 	}
 
 	go func() {
-		if err := launch.Launch(ctx, launch.Options{ConnectToSocket: vm0, SerialPort: os.Stdout}); err != nil {
+		if err := launch.Launch(ctx, launch.Options{
+			ConnectToSocket: vm0,
+			SerialPort:      prefixedStdout("1| "),
+		}); err != nil {
 			log.Fatalf("Failed to launch vm0: %v", err)
 		}
 	}()
@@ -86,7 +113,11 @@
 			GoldenTicket: res.Ticket,
 		}
 
-		if err := launch.Launch(ctx, launch.Options{ConnectToSocket: vm1, EnrolmentConfig: ec, SerialPort: os.Stdout}); err != nil {
+		if err := launch.Launch(ctx, launch.Options{
+			ConnectToSocket: vm1,
+			EnrolmentConfig: ec,
+			SerialPort:      prefixedStdout("2| "),
+		}); err != nil {
 			log.Fatalf("Failed to launch vm1: %v", err)
 		}
 	}()