cmd/buildlet: add revdial test
This replaces the broken test in internal/coordinator/pool.
For golang/go#48803
Change-Id: I7bd9265bba555562ffa7d59169a9c8792ed97d3c
Reviewed-on: https://go-review.googlesource.com/c/build/+/354629
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 0215691..a1cf644 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -247,9 +247,13 @@
log.Printf("Error in serveReverseHealth: %v", err)
}
}()
- if err := dialCoordinator(); err != nil {
+ ln, err := dialCoordinator()
+ if err != nil {
log.Fatalf("Error dialing coordinator: %v", err)
}
+ srv := &http.Server{}
+ err = srv.Serve(ln)
+ log.Printf("http.Serve on reverse connection complete: %v", err)
log.Printf("buildlet reverse mode exiting.")
if *haltEntireOS {
// The coordinator disconnects before doHalt has time to
diff --git a/cmd/buildlet/reverse.go b/cmd/buildlet/reverse.go
index 3c3194d..9d33fe6 100644
--- a/cmd/buildlet/reverse.go
+++ b/cmd/buildlet/reverse.go
@@ -60,7 +60,10 @@
return !strings.HasPrefix(*coordinator, "farmer.golang.org")
}
-func dialCoordinator() error {
+// dialCoordinator dials the coordinator to establish a revdial connection
+// where the returned net.Listener can be used to accept connections from the
+// coordinator.
+func dialCoordinator() (net.Listener, error) {
devMode := isDevReverseMode()
if *hostname == "" {
@@ -108,7 +111,7 @@
}
conn, err := dial(context.Background())
if err != nil {
- return err
+ return nil, err
}
bufr := bufio.NewReader(conn)
@@ -125,28 +128,23 @@
req.Header.Set("X-Go-Builder-Version", strconv.Itoa(buildletVersion))
req.Header.Set("X-Revdial-Version", "2")
if err := req.Write(bufw); err != nil {
- return fmt.Errorf("coordinator /reverse request failed: %v", err)
+ return nil, fmt.Errorf("coordinator /reverse request failed: %v", err)
}
if err := bufw.Flush(); err != nil {
- return fmt.Errorf("coordinator /reverse request flush failed: %v", err)
+ return nil, fmt.Errorf("coordinator /reverse request flush failed: %v", err)
}
resp, err := http.ReadResponse(bufr, req)
if err != nil {
- return fmt.Errorf("coordinator /reverse response failed: %v", err)
+ return nil, fmt.Errorf("coordinator /reverse response failed: %v", err)
}
if resp.StatusCode != 101 {
msg, _ := ioutil.ReadAll(resp.Body)
- return fmt.Errorf("coordinator registration failed; want HTTP status 101; got %v:\n\t%s", resp.Status, msg)
+ return nil, fmt.Errorf("coordinator registration failed; want HTTP status 101; got %v:\n\t%s", resp.Status, msg)
}
log.Printf("Connected to coordinator; reverse dialing active")
- srv := &http.Server{}
ln := revdial.NewListener(conn, dial)
- err = srv.Serve(ln)
- if ln.Closed() { // TODO: this actually wants to know whether an error-free Close was called
- return nil
- }
- return fmt.Errorf("http.Serve on reverse connection complete: %v", err)
+ return ln, nil
}
var coordDialer = &net.Dialer{
@@ -206,8 +204,10 @@
return c, nil
}
+const devMasterKey = "gophers rule"
+
func devBuilderKey(builder string) string {
- h := hmac.New(md5.New, []byte("gophers rule"))
+ h := hmac.New(md5.New, []byte(devMasterKey))
io.WriteString(h, builder)
return fmt.Sprintf("%x", h.Sum(nil))
}
@@ -228,10 +228,3 @@
}
return "/"
}
-
-// TestDialCoordinator dials the coordinator. Exported for testing.
-func TestDialCoordinator() {
- // TODO(crawshaw): move some of this logic out of main to simplify testing hook.
- http.Handle("/status", http.HandlerFunc(handleStatus))
- dialCoordinator()
-}
diff --git a/cmd/buildlet/reverse_test.go b/cmd/buildlet/reverse_test.go
new file mode 100644
index 0000000..d503f6a
--- /dev/null
+++ b/cmd/buildlet/reverse_test.go
@@ -0,0 +1,130 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Same requirements as internal/coordinator/pool/reverse.go.
+//go:build go1.13 && (linux || darwin)
+// +build go1.13
+// +build linux darwin
+
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net"
+ "net/http"
+ "testing"
+ "time"
+
+ "golang.org/x/build"
+ "golang.org/x/build/internal/coordinator/pool"
+ "golang.org/x/build/revdial/v2"
+)
+
+// coordinatorServer creates a server and listener for the coordinator side of
+// revdial. They should be closed when done.
+func coordinatorServer() (*http.Server, net.Listener, error) {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/reverse", pool.HandleReverse)
+ mux.Handle("/revdial", revdial.ConnHandler())
+
+ ln, err := net.Listen("tcp", "")
+ if err != nil {
+ return nil, nil, fmt.Errorf(`net.Listen(":"): %v`, err)
+ }
+
+ cert, err := tls.X509KeyPair([]byte(build.DevCoordinatorCA), []byte(build.DevCoordinatorKey))
+ if err != nil {
+ return nil, nil, fmt.Errorf("error creating TLS cert: %v", err)
+ }
+
+ ln = tls.NewListener(ln, &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ })
+
+ addr := ln.Addr().String()
+ srv := &http.Server{
+ Addr: addr,
+ Handler: mux,
+ }
+ return srv, ln, nil
+}
+
+// testReverseDial verfies that a revdial connection can be established and
+// registered in the coordinator reverse pool at coordAddr.
+func testReverseDial(t *testing.T, coordAddr, hostType string) {
+ t.Helper()
+
+ oldCoordinator := *coordinator
+ defer func() {
+ *coordinator = oldCoordinator
+ }()
+ *coordinator = coordAddr
+
+ // N.B. We don't need to set *hostname to anything in particular as it
+ // is only advisory in the coordinator. It is not used to connect back
+ // to reverse buildlets.
+
+ oldReverseType := *reverseType
+ defer func() {
+ *reverseType = oldReverseType
+ }()
+ *reverseType = hostType
+
+ ln, err := dialCoordinator()
+ if err != nil {
+ t.Fatalf("dialCoordinator got err %v want nil", err)
+ }
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/status", handleStatus)
+ srv := &http.Server{
+ Handler: mux,
+ }
+ c := make(chan error, 1)
+ go func() {
+ c <- srv.Serve(ln)
+ }()
+ defer func() {
+ srv.Close()
+ err := <-c
+ if err != http.ErrServerClosed {
+ t.Errorf("Server shutdown got err %v want ErrServerClosed", err)
+ }
+ }()
+
+ // Verify that we eventually get the "buildlet" registered with the pool.
+ tick := time.NewTicker(10 * time.Millisecond)
+ defer tick.Stop()
+ start := time.Now()
+ for range tick.C {
+ if time.Since(start) > 1*time.Second {
+ t.Fatalf("Buildlet failed to register within 1s.")
+ }
+
+ types := pool.ReversePool().HostTypes()
+ for _, typ := range types {
+ if typ == hostType {
+ // Success!
+ return
+ }
+ }
+ }
+}
+
+// TestReverseDial verfies that a revdial connection can be established and
+// registered in the coordinator reverse pool.
+func TestReverseDial(t *testing.T) {
+ pool.SetBuilderMasterKey([]byte(devMasterKey))
+
+ srv, ln, err := coordinatorServer()
+ if err != nil {
+ t.Fatalf("serveCoordinator got err %v want nil", err)
+ }
+ go srv.Serve(ln)
+ defer srv.Close()
+
+ const hostType = "test-reverse-dial"
+ testReverseDial(t, srv.Addr, hostType)
+}
diff --git a/internal/coordinator/pool/reverse_test.go b/internal/coordinator/pool/reverse_test.go
deleted file mode 100644
index 2ef9d1b..0000000
--- a/internal/coordinator/pool/reverse_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build (linux || darwin) && BROKEN
-// +build linux darwin
-// +build BROKEN
-
-// I get:
-// $ go test -v
-// ./reverse_test.go:15: can't find import: "golang.org/x/build/cmd/buildlet"
-
-package main
-
-import (
- "flag"
- "net"
- "net/http"
- "strings"
- "testing"
- "time"
-
- buildletmain "golang.org/x/build/cmd/buildlet"
-)
-
-func TestReverseDial(t *testing.T) {
- *mode = "dev"
- http.HandleFunc("/reverse", handleReverse)
-
- ln, err := net.Listen("tcp", "")
- if err != nil {
- t.Fatalf(`net.Listen(":"): %v`, err)
- }
- t.Logf("listening on %s...", ln.Addr())
- go serveTLS(ln)
-
- wantModes := "goos-goarch-test1,goos-goarch-test2"
- flag.CommandLine.Set("coordinator", ln.Addr().String())
- flag.CommandLine.Set("reverse", wantModes)
-
- ch := make(chan []string)
- registerBuildlet = func(modes []string) { ch <- modes }
- go buildletmain.TestDialCoordinator()
-
- select {
- case modes := <-ch:
- gotModes := strings.Join(modes, ",")
- if gotModes != wantModes {
- t.Errorf("want buildlet registered with modes %q, got %q", wantModes, gotModes)
- }
- case <-time.After(2 * time.Second):
- t.Error("timeout waiting for buildlet registration")
- }
-}