// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package agent

import (
	"bytes"
	"encoding/base64"
	"io"
	"net"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/serf/client"
	"github.com/hashicorp/serf/serf"
	"github.com/hashicorp/serf/testutil"
)

func testRPCClient(t *testing.T, ip net.IP) (*client.RPCClient, *Agent, *AgentIPC) {
	agentConf := DefaultConfig()
	serfConf := serf.DefaultConfig()

	return testRPCClientWithConfig(t, ip, agentConf, serfConf)
}

// testRPCClient returns an RPCClient connected to an RPC server that
// serves only this connection.
func testRPCClientWithConfig(t *testing.T, ip net.IP, agentConf *Config,
	serfConf *serf.Config) (*client.RPCClient, *Agent, *AgentIPC) {

	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	tw := testutil.TestWriter(t)

	lw := NewLogWriter(512)
	mult := io.MultiWriter(tw, lw)

	agent := testAgentWithConfig(t, ip, agentConf, serfConf, mult)
	ipc := NewAgentIPC(agent, "", l, mult, lw, serfConf.MsgpackUseNewTimeFormat)

	rpcClient, err := client.NewRPCClient(l.Addr().String())
	if err != nil {
		ipc.Shutdown()
		t.Fatalf("err: %v", err)
	}

	return rpcClient, agent, ipc
}

func findMember(t *testing.T, members []serf.Member, name string) serf.Member {
	for _, m := range members {
		if m.Name == name {
			return m
		}
	}
	t.Fatalf("%s not found", name)
	return serf.Member{}
}

func TestRPCClientForceLeave(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	a2 := testAgent(t, ip2, nil)
	defer a2.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	s2Addr := a2.conf.MemberlistConfig.BindAddr
	if _, err := a1.Join([]string{a2.conf.NodeName + "/" + s2Addr}, false); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	if err := a2.Shutdown(); err != nil {
		t.Fatalf("err: %v", err)
	}

	start := time.Now()
WAIT:
	time.Sleep(a1.conf.MemberlistConfig.ProbeInterval * 3)
	m := a1.Serf().Members()
	if len(m) != 2 {
		t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
	}
	if findMember(t, m, a2.conf.NodeName).Status != serf.StatusFailed && time.Now().Sub(start) < 3*time.Second {
		goto WAIT
	}

	if err := client.ForceLeave(a2.conf.NodeName); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	m = a1.Serf().Members()
	if len(m) != 2 {
		t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
	}

	if findMember(t, m, a2.conf.NodeName).Status != serf.StatusLeft {
		t.Fatalf("should be left: %#v", m[1])
	}
}

func TestRPCClientForceLeave_prune(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a1.Shutdown()

	a2 := testAgent(t, ip2, nil)
	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a2.Shutdown()

	testutil.Yield()

	s2Addr := a2.conf.MemberlistConfig.BindAddr
	if _, err := a1.Join([]string{a2.conf.NodeName + "/" + s2Addr}, false); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	if err := a2.Shutdown(); err != nil {
		t.Fatalf("err: %v", err)
	}

	start := time.Now()
WAIT:
	time.Sleep(a1.conf.MemberlistConfig.ProbeInterval * 3)
	m := a1.Serf().Members()
	if len(m) != 2 {
		t.Fatalf("should have 2 members: %#v", a1.Serf().Members())
	}
	if findMember(t, m, a2.conf.NodeName).Status != serf.StatusFailed && time.Now().Sub(start) < 3*time.Second {
		goto WAIT
	}

	if err := client.ForceLeavePrune(a2.conf.NodeName); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	m = a1.Serf().Members()
	if len(m) != 1 {
		t.Fatalf("should have 1 members: %#v", a1.Serf().Members())
	}

}

func TestRPCClientJoin(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	a2 := testAgent(t, ip2, nil)
	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a2.Shutdown()

	testutil.Yield()

	n, err := client.Join([]string{a2.conf.NodeName + "/" + a2.conf.MemberlistConfig.BindAddr}, false)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if n != 1 {
		t.Fatalf("n != 1: %d", n)
	}
}

func TestRPCClientMembers(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	a2 := testAgent(t, ip2, nil)
	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a2.Shutdown()

	testutil.Yield()

	mem, err := client.Members()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 1 {
		t.Fatalf("bad: %#v", mem)
	}

	_, err = client.Join([]string{a2.conf.NodeName + "/" + a2.conf.MemberlistConfig.BindAddr}, false)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	mem, err = client.Members()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 2 {
		t.Fatalf("bad: %#v", mem)
	}
}

func TestRPCClientMembersFiltered(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	a2 := testAgent(t, ip2, nil)
	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a2.Shutdown()

	testutil.Yield()

	_, err := client.Join([]string{a2.conf.NodeName + "/" + a2.conf.MemberlistConfig.BindAddr}, false)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	err = client.UpdateTags(map[string]string{
		"tag1": "val1",
		"tag2": "val2",
	}, []string{})

	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	testutil.Yield()

	// Make sure that filters work on member names
	mem, err := client.MembersFiltered(map[string]string{}, "", ".*")
	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	if len(mem) == 0 {
		t.Fatalf("should have matched more than 0 members")
	}

	mem, err = client.MembersFiltered(map[string]string{}, "", "bad")
	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	if len(mem) != 0 {
		t.Fatalf("should have matched 0 members: %#v", mem)
	}

	// Make sure that filters work on member tags
	mem, err = client.MembersFiltered(map[string]string{"tag1": "val.*"}, "", "")
	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	if len(mem) != 1 {
		t.Fatalf("should have matched 1 member: %#v", mem)
	}

	// Make sure tag filters work on multiple tags
	mem, err = client.MembersFiltered(map[string]string{
		"tag1": "val.*",
		"tag2": "val2",
	}, "", "")

	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	if len(mem) != 1 {
		t.Fatalf("should have matched one member: %#v", mem)
	}

	// Make sure all tags match when multiple tags are passed
	mem, err = client.MembersFiltered(map[string]string{
		"tag1": "val1",
		"tag2": "bad",
	}, "", "")

	if err != nil {
		t.Fatalf("bad: %s", err)
	}

	if len(mem) != 0 {
		t.Fatalf("should have matched 0 members: %#v", mem)
	}

	// Make sure that filters work on member status
	if err := client.ForceLeave(a2.conf.NodeName); err != nil {
		t.Fatalf("bad: %s", err)
	}

	mem, err = client.MembersFiltered(map[string]string{}, "alive", "")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 1 {
		t.Fatalf("should have matched 1 member: %#v", mem)
	}

	mem, err = client.MembersFiltered(map[string]string{}, "leaving", "")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 1 {
		t.Fatalf("should have matched 1 member: %#v", mem)
	}
}

func TestRPCClientUserEvent(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	handler := new(MockEventHandler)
	a1.RegisterEventHandler(handler)

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	if err := client.UserEvent("deploy", []byte("foo"), false); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	handler.Lock()
	defer handler.Unlock()

	if len(handler.Events) == 0 {
		t.Fatal("no events")
	}

	serfEvent, ok := handler.Events[len(handler.Events)-1].(serf.UserEvent)
	if !ok {
		t.Fatalf("bad: %#v", serfEvent)
	}

	if serfEvent.Name != "deploy" {
		t.Fatalf("bad: %#v", serfEvent)
	}

	if string(serfEvent.Payload) != "foo" {
		t.Fatalf("bad: %#v", serfEvent)
	}
}

func TestRPCClientLeave(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	testutil.Yield()

	if err := client.Leave(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	select {
	case <-a1.ShutdownCh():
	default:
		t.Fatalf("agent should be shutdown!")
	}
}

func TestRPCClientMonitor(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	eventCh := make(chan string, 64)
	if handle, err := client.Monitor("debug", eventCh); err != nil {
		t.Fatalf("err: %v", err)
	} else {
		defer client.Stop(handle)
	}

	testutil.Yield()

	select {
	case e := <-eventCh:
		if !strings.Contains(e, "Accepted client") {
			t.Fatalf("bad: %s", e)
		}
	default:
		t.Fatalf("should have backlog")
	}

	// Drain the rest of the messages as we know it
	drainEventCh(eventCh)

	// Join a bad thing to generate more events
	a1.Join(nil, false)

	testutil.Yield()

	select {
	case e := <-eventCh:
		if !strings.Contains(e, "joining") {
			t.Fatalf("bad: %s", e)
		}
	default:
		t.Fatalf("should have message")
	}
}

func TestRPCClientStream_User(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	eventCh := make(chan map[string]interface{}, 64)
	if handle, err := client.Stream("user", eventCh); err != nil {
		t.Fatalf("err: %v", err)
	} else {
		defer client.Stop(handle)
	}

	testutil.Yield()

	if err := client.UserEvent("deploy", []byte("foo"), false); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	select {
	case e := <-eventCh:
		if e["Event"].(string) != "user" {
			t.Fatalf("bad event: %#v", e)
		}
		if e["LTime"].(int64) != 1 {
			t.Fatalf("bad event: %#v", e)
		}
		if e["Name"].(string) != "deploy" {
			t.Fatalf("bad event: %#v", e)
		}
		if bytes.Compare(e["Payload"].([]byte), []byte("foo")) != 0 {
			t.Fatalf("bad event: %#v", e)
		}
		if e["Coalesce"].(bool) != false {
			t.Fatalf("bad event: %#v", e)
		}

	default:
		t.Fatalf("should have event")
	}
}

func TestRPCClientStream_Member(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	ip2, returnFn2 := testutil.TakeIP()
	defer returnFn2()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	a2 := testAgent(t, ip2, nil)
	if err := a2.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	defer a2.Shutdown()

	testutil.Yield()

	eventCh := make(chan map[string]interface{}, 64)
	if handle, err := client.Stream("*", eventCh); err != nil {
		t.Fatalf("err: %v", err)
	} else {
		defer client.Stop(handle)
	}

	testutil.Yield()

	s2Addr := a2.conf.MemberlistConfig.BindAddr
	if _, err := a1.Join([]string{a2.conf.NodeName + "/" + s2Addr}, false); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	select {
	case e := <-eventCh:
		if e["Event"].(string) != "member-join" {
			t.Fatalf("bad event: %#v", e)
		}

		members := e["Members"].([]interface{})
		if len(members) != 1 {
			t.Fatalf("should have 1 member")
		}
		member := members[0].(map[interface{}]interface{})

		if _, ok := member["Name"].(string); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["Addr"].([]uint8); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["Port"].(uint64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["Tags"].(map[interface{}]interface{}); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if stat, _ := member["Status"].(string); stat != "alive" {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["ProtocolMin"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["ProtocolMax"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["ProtocolCur"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["DelegateMin"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["DelegateMax"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}
		if _, ok := member["DelegateCur"].(int64); !ok {
			t.Fatalf("bad event: %#v", e)
		}

	default:
		t.Fatalf("should have event")
	}
}

func TestRPCClientUpdateTags(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	mem, err := client.Members()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 1 {
		t.Fatalf("bad: %#v", mem)
	}

	m0 := mem[0]
	if _, ok := m0.Tags["testing"]; ok {
		t.Fatalf("have testing tag")
	}

	if err := client.UpdateTags(map[string]string{"testing": "1"}, nil); err != nil {
		t.Fatalf("err: %v", err)
	}

	mem, err = client.Members()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if len(mem) != 1 {
		t.Fatalf("bad: %#v", mem)
	}

	m0 = mem[0]
	if _, ok := m0.Tags["testing"]; !ok {
		t.Fatalf("missing testing tag")
	}
}

func TestRPCClientQuery(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	cl, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer cl.Close()
	defer a1.Shutdown()

	handler := new(MockQueryHandler)
	handler.Response = []byte("ok")
	a1.RegisterEventHandler(handler)

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	ackCh := make(chan string, 1)
	respCh := make(chan client.NodeResponse, 1)
	params := client.QueryParam{
		RequestAck: true,
		Timeout:    200 * time.Millisecond,
		Name:       "deploy",
		Payload:    []byte("foo"),
		AckCh:      ackCh,
		RespCh:     respCh,
	}
	if err := cl.Query(&params); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	handler.Lock()
	defer handler.Unlock()

	if len(handler.Queries) == 0 {
		t.Fatal("no queries")
	}

	query := handler.Queries[0]
	if query.Name != "deploy" {
		t.Fatalf("bad: %#v", query)
	}

	if string(query.Payload) != "foo" {
		t.Fatalf("bad: %#v", query)
	}

	select {
	case a := <-ackCh:
		if a != a1.conf.NodeName {
			t.Fatalf("Bad ack from: %v", a)
		}
	default:
		t.Fatalf("missing ack")
	}

	select {
	case r := <-respCh:
		if r.From != a1.conf.NodeName {
			t.Fatalf("Bad resp from: %v", r)
		}
		if string(r.Payload) != "ok" {
			t.Fatalf("Bad resp from: %v", r)
		}
	default:
		t.Fatalf("missing response")
	}
}

func TestRPCClientStream_Query(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	cl, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer cl.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	eventCh := make(chan map[string]interface{}, 64)
	if handle, err := cl.Stream("query", eventCh); err != nil {
		t.Fatalf("err: %v", err)
	} else {
		defer cl.Stop(handle)
	}

	testutil.Yield()

	params := client.QueryParam{
		Timeout: 200 * time.Millisecond,
		Name:    "deploy",
		Payload: []byte("foo"),
	}
	if err := cl.Query(&params); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	select {
	case e := <-eventCh:
		if e["Event"].(string) != "query" {
			t.Fatalf("bad query: %#v", e)
		}
		if e["ID"].(int64) != 1 {
			t.Fatalf("bad query: %#v", e)
		}
		if e["LTime"].(int64) != 1 {
			t.Fatalf("bad query: %#v", e)
		}
		if e["Name"].(string) != "deploy" {
			t.Fatalf("bad query: %#v", e)
		}
		if bytes.Compare(e["Payload"].([]byte), []byte("foo")) != 0 {
			t.Fatalf("bad query: %#v", e)
		}

	default:
		t.Fatalf("should have query")
	}
}

func TestRPCClientStream_Query_Respond(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	cl, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer cl.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	eventCh := make(chan map[string]interface{}, 64)
	if handle, err := cl.Stream("query", eventCh); err != nil {
		t.Fatalf("err: %v", err)
	} else {
		defer cl.Stop(handle)
	}

	testutil.Yield()

	ackCh := make(chan string, 1)
	respCh := make(chan client.NodeResponse, 1)
	params := client.QueryParam{
		RequestAck: true,
		Timeout:    500 * time.Millisecond,
		Name:       "ping",
		AckCh:      ackCh,
		RespCh:     respCh,
	}
	if err := cl.Query(&params); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	select {
	case e := <-eventCh:
		if e["Event"].(string) != "query" {
			t.Fatalf("bad query: %#v", e)
		}
		if e["Name"].(string) != "ping" {
			t.Fatalf("bad query: %#v", e)
		}

		// Send a response
		id := e["ID"].(int64)
		if err := cl.Respond(uint64(id), []byte("pong")); err != nil {
			t.Fatalf("err: %v", err)
		}

	default:
		t.Fatalf("should have query")
	}

	testutil.Yield()

	// Should have ack
	select {
	case a := <-ackCh:
		if a != a1.conf.NodeName {
			t.Fatalf("Bad ack from: %v", a)
		}
	default:
		t.Fatalf("missing ack")
	}

	// Should have response
	select {
	case r := <-respCh:
		if r.From != a1.conf.NodeName {
			t.Fatalf("Bad resp from: %v", r)
		}
		if string(r.Payload) != "pong" {
			t.Fatalf("Bad resp from: %v", r)
		}
	default:
		t.Fatalf("missing response")
	}
}

func TestRPCClientAuth(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	cl, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer cl.Close()
	defer a1.Shutdown()

	// Setup an auth key
	ipc.authKey = "foobar"

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}
	testutil.Yield()

	if err := cl.UserEvent("deploy", nil, false); err.Error() != authRequired {
		t.Fatalf("err: %v", err)
	}
	testutil.Yield()

	config := client.Config{Addr: ipc.listener.Addr().String(), AuthKey: "foobar"}
	rpcClient, err := client.ClientFromConfig(&config)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	defer rpcClient.Close()

	if err := rpcClient.UserEvent("deploy", nil, false); err != nil {
		t.Fatalf("err: %v", err)
	}
}

func TestRPCClient_Keys_EncryptionDisabledError(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Failed installing key
	failures, err := client.InstallKey("5K9OtfP7efFrNKe5WCQvXvnaXJ5cWP0SvXiwe0kkjM4=")
	if err == nil {
		t.Fatalf("expected encryption disabled error")
	}
	if _, ok := failures[a1.conf.NodeName]; !ok {
		t.Fatalf("expected error from node %s", a1.conf.NodeName)
	}

	// Failed using key
	failures, err = client.UseKey("5K9OtfP7efFrNKe5WCQvXvnaXJ5cWP0SvXiwe0kkjM4=")
	if err == nil {
		t.Fatalf("expected encryption disabled error")
	}
	if _, ok := failures[a1.conf.NodeName]; !ok {
		t.Fatalf("expected error from node %s", a1.conf.NodeName)
	}

	// Failed removing key
	failures, err = client.RemoveKey("5K9OtfP7efFrNKe5WCQvXvnaXJ5cWP0SvXiwe0kkjM4=")
	if err == nil {
		t.Fatalf("expected encryption disabled error")
	}
	if _, ok := failures[a1.conf.NodeName]; !ok {
		t.Fatalf("expected error from node %s", a1.conf.NodeName)
	}

	// Failed listing keys
	_, _, failures, err = client.ListKeys()
	if err == nil {
		t.Fatalf("expected encryption disabled error")
	}
	if _, ok := failures[a1.conf.NodeName]; !ok {
		t.Fatalf("expected error from node %s", a1.conf.NodeName)
	}
}

func TestRPCClient_Keys(t *testing.T) {
	newKey := "5K9OtfP7efFrNKe5WCQvXvnaXJ5cWP0SvXiwe0kkjM4="
	existing := "T9jncgl9mbLus+baTTa7q7nPSUrXwbDi2dhbtqir37s="
	existingBytes, err := base64.StdEncoding.DecodeString(existing)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	agentConf := DefaultConfig()
	serfConf := serf.DefaultConfig()
	serfConf.MemberlistConfig.SecretKey = existingBytes

	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClientWithConfig(t, ip1, agentConf, serfConf)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	keys, num, _, err := client.ListKeys()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if _, ok := keys[newKey]; ok {
		t.Fatalf("have new key: %s", newKey)
	}

	// Trying to use a key that doesn't exist errors
	if _, err := client.UseKey(newKey); err == nil {
		t.Fatalf("expected use-key error: %s", newKey)
	}

	// Keyring should not contain new key at this point
	keys, _, _, err = client.ListKeys()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if _, ok := keys[newKey]; ok {
		t.Fatalf("have new key: %s", newKey)
	}

	// Invalid key installation throws an error
	if _, err := client.InstallKey("badkey"); err == nil {
		t.Fatalf("expected bad key error")
	}

	// InstallKey should succeed
	if _, err := client.InstallKey(newKey); err != nil {
		t.Fatalf("err: %v", err)
	}

	// InstallKey is idempotent
	if _, err := client.InstallKey(newKey); err != nil {
		t.Fatalf("err: %v", err)
	}

	// New key should now appear in the list of keys
	keys, num, _, err = client.ListKeys()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if num != 1 {
		t.Fatalf("expected 1 member total, got %d", num)
	}
	if _, ok := keys[newKey]; !ok {
		t.Fatalf("key not found: %s", newKey)
	}

	// Counter of installed copies of new key should be 1
	if keys[newKey] != 1 {
		t.Fatalf("expected 1 member with key %s, have %d", newKey, keys[newKey])
	}

	// Deleting primary key should return error
	if _, err := client.RemoveKey(existing); err == nil {
		t.Fatalf("expected error deleting primary key: %s", newKey)
	}

	// UseKey succeeds when given a key that exists
	if _, err := client.UseKey(newKey); err != nil {
		t.Fatalf("err: %v", err)
	}

	// UseKey is idempotent
	if _, err := client.UseKey(newKey); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Removing a non-primary key should succeed
	if _, err := client.RemoveKey(newKey); err == nil {
		t.Fatalf("expected error deleting primary key: %s", newKey)
	}

	// RemoveKey is idempotent
	if _, err := client.RemoveKey(existing); err != nil {
		t.Fatalf("err: %v", err)
	}
}

func TestRPCClientStats(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	stats, err := client.Stats()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if stats["agent"]["name"] != a1.conf.NodeName {
		t.Fatalf("bad: %v", stats)
	}
}

func TestRPCClientGetCoordinate(t *testing.T) {
	ip1, returnFn1 := testutil.TakeIP()
	defer returnFn1()

	client, a1, ipc := testRPCClient(t, ip1)
	defer ipc.Shutdown()
	defer client.Close()
	defer a1.Shutdown()

	if err := a1.Start(); err != nil {
		t.Fatalf("err: %v", err)
	}

	testutil.Yield()

	coord, err := client.GetCoordinate(a1.conf.NodeName)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if coord == nil {
		t.Fatalf("should have gotten a coordinate")
	}

	coord, err = client.GetCoordinate("nope")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if coord != nil {
		t.Fatalf("should have not gotten a coordinate")
	}
}
