metropolis/*: confine etcd output in tests

The etcd test cluster logic produces some very chatty logs that end up
in stdout.

This confines the etcd logs themselves, as well as gRPC logs that the
test logic also always enables by default.

Change-Id: I1070f14b20e870865b510ae24015402c0469ceff
Reviewed-on: https://review.monogon.dev/c/monogon/+/1487
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/curator/BUILD.bazel b/metropolis/node/core/curator/BUILD.bazel
index 24c2d97..dc39f38 100644
--- a/metropolis/node/core/curator/BUILD.bazel
+++ b/metropolis/node/core/curator/BUILD.bazel
@@ -79,7 +79,9 @@
         "@io_etcd_go_etcd_client_v3//:client",
         "@io_etcd_go_etcd_tests_v3//integration",
         "@org_golang_google_grpc//:go_default_library",
+        "@org_golang_google_grpc//grpclog",
         "@org_golang_google_grpc//test/bufconn",
         "@org_golang_google_protobuf//proto",
+        "@org_uber_go_zap//:zap",
     ],
 )
diff --git a/metropolis/node/core/curator/curator_test.go b/metropolis/node/core/curator/curator_test.go
index e9674f5..c4ae80a 100644
--- a/metropolis/node/core/curator/curator_test.go
+++ b/metropolis/node/core/curator/curator_test.go
@@ -9,11 +9,14 @@
 
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"go.etcd.io/etcd/tests/v3/integration"
+	"go.uber.org/zap"
+	"google.golang.org/grpc/grpclog"
 
 	"source.monogon.dev/metropolis/node/core/consensus"
 	"source.monogon.dev/metropolis/node/core/identity"
 	"source.monogon.dev/metropolis/node/core/rpc"
 	"source.monogon.dev/metropolis/pkg/event"
+	"source.monogon.dev/metropolis/pkg/logtree"
 	"source.monogon.dev/metropolis/pkg/supervisor"
 )
 
@@ -44,7 +47,7 @@
 
 // newDut creates a new dut harness for a curator instance, connected to a given
 // etcd endpoint.
-func newDut(ctx context.Context, t *testing.T, endpoint string, n *identity.NodeCredentials) *dut {
+func newDut(ctx context.Context, lt *logtree.LogTree, t *testing.T, endpoint string, n *identity.NodeCredentials) *dut {
 	t.Helper()
 	// Create new etcd client to the given endpoint.
 	cli, err := clientv3.New(clientv3.Config{
@@ -53,6 +56,7 @@
 		DialKeepAliveTime:    1 * time.Second,
 		DialKeepAliveTimeout: 1 * time.Second,
 		Context:              ctx,
+		Logger:               logtree.Zapify(lt.MustLeveledFor("client"), zap.WarnLevel),
 	})
 	if err != nil {
 		t.Fatalf("clientv3.New: %v", err)
@@ -175,12 +179,20 @@
 // of them respond correctly to election, partitioning and subsequent
 // re-election.
 func TestLeaderElectionStatus(t *testing.T) {
+	lt := logtree.New()
+	logtree.PipeAllToTest(t, lt)
+
 	ctx, ctxC := context.WithCancel(context.Background())
 	cfg := integration.ClusterConfig{
 		Size:                 3,
 		GRPCKeepAliveMinTime: time.Millisecond,
+		LoggerBuilder: func(memberName string) *zap.Logger {
+			dn := logtree.DN("etcd." + memberName)
+			return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+		},
 	}
 	integration.BeforeTestExternal(t)
+	grpclog.SetLoggerV2(logtree.GRPCify(lt.MustLeveledFor("grpc")))
 	cluster = integration.NewClusterV3(t, &cfg)
 	t.Cleanup(func() {
 		ctxC()
@@ -208,7 +220,7 @@
 	dutC := make(chan *dut)
 	supervisor.TestHarness(t, func(ctx context.Context) error {
 		for e, n := range endpointToNum {
-			dutC <- newDut(ctx, t, e, ephemeral.Nodes[n])
+			dutC <- newDut(ctx, lt, t, e, ephemeral.Nodes[n])
 		}
 		close(dutC)
 		supervisor.Signal(ctx, supervisor.SignalHealthy)
diff --git a/metropolis/node/core/curator/impl_leader_test.go b/metropolis/node/core/curator/impl_leader_test.go
index b5b46de..5063fef 100644
--- a/metropolis/node/core/curator/impl_leader_test.go
+++ b/metropolis/node/core/curator/impl_leader_test.go
@@ -15,7 +15,9 @@
 	"time"
 
 	"go.etcd.io/etcd/tests/v3/integration"
+	"go.uber.org/zap"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/grpclog"
 	"google.golang.org/grpc/test/bufconn"
 	"google.golang.org/protobuf/proto"
 
@@ -52,8 +54,13 @@
 
 	// Start a single-node etcd cluster.
 	integration.BeforeTestExternal(t)
+	grpclog.SetLoggerV2(logtree.GRPCify(lt.MustLeveledFor("grpc")))
 	cluster := integration.NewClusterV3(t, &integration.ClusterConfig{
 		Size: 1,
+		LoggerBuilder: func(memberName string) *zap.Logger {
+			dn := logtree.DN("etcd." + memberName)
+			return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+		},
 	})
 	// Clean up the etcd cluster and cancel the context on test end. We don't just
 	// use a context because we need the cluster to terminate synchronously before
diff --git a/metropolis/pkg/event/etcd/BUILD.bazel b/metropolis/pkg/event/etcd/BUILD.bazel
index 51ad5c1..37df147 100644
--- a/metropolis/pkg/event/etcd/BUILD.bazel
+++ b/metropolis/pkg/event/etcd/BUILD.bazel
@@ -20,10 +20,13 @@
     deps = [
         "//metropolis/node/core/consensus/client",
         "//metropolis/pkg/event",
+        "//metropolis/pkg/logtree",
         "@io_etcd_go_etcd_api_v3//v3rpc/rpctypes",
         "@io_etcd_go_etcd_client_pkg_v3//testutil",
         "@io_etcd_go_etcd_client_v3//:client",
         "@io_etcd_go_etcd_tests_v3//integration",
         "@org_golang_google_grpc//codes",
+        "@org_golang_google_grpc//grpclog",
+        "@org_uber_go_zap//:zap",
     ],
 )
diff --git a/metropolis/pkg/event/etcd/etcd_test.go b/metropolis/pkg/event/etcd/etcd_test.go
index 81aee51..e7e1227 100644
--- a/metropolis/pkg/event/etcd/etcd_test.go
+++ b/metropolis/pkg/event/etcd/etcd_test.go
@@ -16,10 +16,13 @@
 	"go.etcd.io/etcd/client/pkg/v3/testutil"
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"go.etcd.io/etcd/tests/v3/integration"
+	"go.uber.org/zap"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/grpclog"
 
 	"source.monogon.dev/metropolis/node/core/consensus/client"
 	"source.monogon.dev/metropolis/pkg/event"
+	"source.monogon.dev/metropolis/pkg/logtree"
 )
 
 var (
@@ -29,14 +32,22 @@
 
 // TestMain brings up a 3 node etcd cluster for tests to use.
 func TestMain(m *testing.M) {
+	// This logtree's data is not output anywhere.
+	lt := logtree.New()
+
 	cfg := integration.ClusterConfig{
 		Size:                 3,
 		GRPCKeepAliveMinTime: time.Millisecond,
+		LoggerBuilder: func(memberName string) *zap.Logger {
+			dn := logtree.DN("etcd." + memberName)
+			return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+		},
 	}
 	tb, cancel := testutil.NewTestingTBProthesis("curator")
 	defer cancel()
 	flag.Parse()
 	integration.BeforeTestExternal(tb)
+	grpclog.SetLoggerV2(logtree.GRPCify(lt.MustLeveledFor("grpc")))
 	cluster = integration.NewClusterV3(tb, &cfg)
 	endpoints = make([]string, 3)
 	for i := range endpoints {
diff --git a/metropolis/pkg/pki/BUILD.bazel b/metropolis/pkg/pki/BUILD.bazel
index ec3babe..97e7fb1 100644
--- a/metropolis/pkg/pki/BUILD.bazel
+++ b/metropolis/pkg/pki/BUILD.bazel
@@ -28,7 +28,9 @@
     embed = [":pki"],
     deps = [
         "//metropolis/node/core/consensus/client",
+        "//metropolis/pkg/logtree",
         "@io_etcd_go_etcd_client_pkg_v3//testutil",
         "@io_etcd_go_etcd_tests_v3//integration",
+        "@org_uber_go_zap//:zap",
     ],
 )
diff --git a/metropolis/pkg/pki/certificate_test.go b/metropolis/pkg/pki/certificate_test.go
index d38b559..19baf94 100644
--- a/metropolis/pkg/pki/certificate_test.go
+++ b/metropolis/pkg/pki/certificate_test.go
@@ -10,15 +10,24 @@
 
 	"go.etcd.io/etcd/client/pkg/v3/testutil"
 	"go.etcd.io/etcd/tests/v3/integration"
+	"go.uber.org/zap"
+
+	"source.monogon.dev/metropolis/pkg/logtree"
 )
 
 // TestManaged ensures Managed Certificates work, including re-ensuring
 // certificates with the same data and issuing subordinate certificates.
 func TestManaged(t *testing.T) {
+	lt := logtree.New()
+	logtree.PipeAllToTest(t, lt)
 	tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
 	defer cancel()
 	cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
 		Size: 1,
+		LoggerBuilder: func(memberName string) *zap.Logger {
+			dn := logtree.DN("etcd." + memberName)
+			return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+		},
 	})
 	cl := cluster.Client(0)
 	defer cluster.Terminate(tb)
@@ -103,10 +112,16 @@
 // re-Ensuring certificates with the same public key, and attempting to re-issue
 // the same certificate with a different public key (which should fail).
 func TestExternal(t *testing.T) {
+	lt := logtree.New()
+	logtree.PipeAllToTest(t, lt)
 	tb, cancel := testutil.NewTestingTBProthesis("pki-managed")
 	defer cancel()
 	cluster := integration.NewClusterV3(tb, &integration.ClusterConfig{
 		Size: 1,
+		LoggerBuilder: func(memberName string) *zap.Logger {
+			dn := logtree.DN("etcd." + memberName)
+			return logtree.Zapify(lt.MustLeveledFor(dn), zap.WarnLevel)
+		},
 	})
 	cl := cluster.Client(0)
 	defer cluster.Terminate(tb)