diff --git a/node/http.go b/node/http.go index ac468ddd7fb8a8c4dbdafdce46d734c1cb5fece3..f1bf24a2f81da254dca3f270ac89d9407d2e39a8 100644 --- a/node/http.go +++ b/node/http.go @@ -63,7 +63,7 @@ func newHTTPHandler(n *Node, icecastPort int, domain string) http.Handler { // there). Using command-line flags it is possible to disable // the default debug pages, or to restrict access to localhost. if !*disableDebugHandlers { - http.Handle("/debug/lbv2", n.lb.lb) + mux.Handle("/debug/lbv2", n.lb.lb) var h http.Handler = http.DefaultServeMux if *restrictDebugHandlers { h = withLocalhost(h) diff --git a/node/server_test.go b/node/server_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3dbe603bcfc049f6a421c9641a49d43c0cbaecc8 --- /dev/null +++ b/node/server_test.go @@ -0,0 +1,167 @@ +package node + +import ( + "bytes" + "context" + "fmt" + "log" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "testing" + "time" + + "go.etcd.io/etcd/clientv3/concurrency" + "go.etcd.io/etcd/embed" + "go.etcd.io/etcd/etcdserver/api/v3client" + + "git.autistici.org/ale/autoradio/client" + pb "git.autistici.org/ale/autoradio/proto" +) + +var testAudio = []byte("trust me, I'm an mp3!") + +func fakeAudioServer() (*httptest.Server, int) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Write(testAudio) + })) + // Extract the port from the URL. + port := 80 + u, _ := url.Parse(s.URL) + if _, portStr, _ := net.SplitHostPort(u.Host); portStr != "" { + port, _ = strconv.Atoi(portStr) + } + return s, port +} + +func fakeDialer(real string) *http.Transport { + d := new(net.Dialer) + return &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return d.DialContext(ctx, network, real) + }, + } +} + +func TestServer(t *testing.T) { + cfg := embed.NewConfig() + cfg.Dir = "default.etcd" + defer os.RemoveAll(cfg.Dir) + e, err := embed.StartEtcd(cfg) + if err != nil { + t.Fatalf("StartEtcd: %v", err) + } + defer e.Close() + <-e.Server.ReadyNotify() + + cli := v3client.New(e.Server) + session, _ := concurrency.NewSession(cli, concurrency.WithTTL(2)) + ctx, cancel := context.WithCancel(context.Background()) + + upstream, icecastPort := fakeAudioServer() + defer upstream.Close() + + var nodes []*Node + var servers []*Server + + for i := 0; i < 2; i++ { + conf := &Config{ + Domain: "localhost", + Nameservers: []string{"127.0.0.1"}, + DNSPort: 4004 + i, + HTTPPort: 4104 + i, + MetricsPort: 4204 + i, + GossipPort: 4404 + i, + IcecastPort: icecastPort, + PeerAddr: net.ParseIP("127.0.0.1"), + } + + n, err := New( + ctx, + session, + &fakeIcecast{}, + fmt.Sprintf("node%d", i+1), + []net.IP{net.ParseIP("127.0.0.1")}, + net.ParseIP("127.0.0.1"), + conf.GossipPort, + "random", + 0, 0, + ) + if err != nil { + t.Fatalf("NewNode: %v", err) + } + srv, err := NewServer(ctx, cli, n, conf) + if err != nil { + t.Fatalf("NewServer: %v", err) + } + + nodes = append(nodes, n) + servers = append(servers, srv) + } + + go func() { + time.Sleep(10 * time.Second) + log.Printf("stopping everything") + cancel() + }() + + // Ping a node until it's alive. + ok := false + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + resp, err := http.Get("http://localhost:4104/") + if err == nil { + resp.Body.Close() + log.Printf("node0 healthz response status: %s", resp.Status) + ok = true + break + } + log.Printf("node0 healthz failure: %v", err) + + time.Sleep(1 * time.Second) + } + if !ok { + t.Fatal("timed out waiting for node0 to become healthy") + } + + // Add a stream via the client API. + ac := client.New(cli) + if err := ac.SetMount(ctx, &pb.Mount{ + Path: "/stream.ogg", + SourceUsername: "user1", + SourcePassword: "password1", + }); err != nil { + t.Fatalf("SetMount: %v", err) + } + log.Printf("stream /stream.ogg created successfully") + + // Try to read from the stream and verify the upstream data. + // Force connections always to node1 (the HTTP redirect will + // have a DNS name, so we need to override that). + hc := &http.Client{ + Transport: fakeDialer("127.0.0.1:4104"), + } + resp, err := hc.Get("http://localhost:4104/stream.ogg") + if err != nil { + t.Fatalf("Get(/stream.ogg): %v", err) + } + defer resp.Body.Close() + data := make([]byte, 1024) + n, _ := resp.Body.Read(data) + if !bytes.Equal(testAudio, data[:n]) { + t.Fatalf("bad response: %s", data[:n]) + } + + // Ok we're done, exit early. + cancel() + + for _, s := range servers { + s.Wait() + } + for _, n := range nodes { + n.Wait() + } +}