mirror of https://github.com/hashicorp/consul
Browse Source
TestAgent will replace the following mechanisms to start test agents in subsequent requests: * makeAgentXXX * makeDNSServerXXX * makeHTTPServerXXX * testServer * httpTestpull/3037/head
Frank Schroeder
8 years ago
1 changed files with 255 additions and 0 deletions
@ -0,0 +1,255 @@
|
||||
package agent |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"math/rand" |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/hashicorp/consul/consul" |
||||
"github.com/hashicorp/consul/consul/structs" |
||||
"github.com/hashicorp/consul/logger" |
||||
"github.com/hashicorp/consul/testutil/retry" |
||||
"github.com/hashicorp/consul/types" |
||||
"github.com/hashicorp/consul/version" |
||||
uuid "github.com/hashicorp/go-uuid" |
||||
) |
||||
|
||||
var TempDir = "/tmp" |
||||
|
||||
// TestAgent encapsulates an Agent with a default configuration and
|
||||
// startup procedure suitable for testing. It panics if there are errors
|
||||
// during creation or startup instead of returning errors. It manages a
|
||||
// temporary data directory which is removed after shutdown.
|
||||
//
|
||||
// todo(fs): do we need the temp data dir if we run in dev mode?
|
||||
type TestAgent struct { |
||||
// Name is an optional name of the agent.
|
||||
Name string |
||||
|
||||
// Config is the agent configuration. If Config is nil then
|
||||
// TestConfig() is used. If Config.DataDir is set then it is
|
||||
// the callers responsibility to clean up the data directory.
|
||||
// Otherwise, a temporary data directory is created and removed
|
||||
// when Shutdown() is called.
|
||||
Config *Config |
||||
|
||||
// LogOutput is the sink for the logs. If nil, logs are written
|
||||
// to os.Stderr.
|
||||
LogOutput io.Writer |
||||
|
||||
// LogWriter is used for streaming logs.
|
||||
LogWriter *logger.LogWriter |
||||
|
||||
// DataDir is the data directory which is used when Config.DataDir
|
||||
// is not set. It is created automatically and removed when
|
||||
// Shutdown() is called.
|
||||
DataDir string |
||||
|
||||
// Key is the optional encryption key for the LAN and WAN keyring.
|
||||
Key string |
||||
|
||||
// dns is a reference to the first started DNS endpoint.
|
||||
// It is valid after Start().
|
||||
dns *DNSServer |
||||
|
||||
// srv is a reference to the first started HTTP endpoint.
|
||||
// It is valid after Start().
|
||||
srv *HTTPServer |
||||
|
||||
// Agent is the embedded consul agent.
|
||||
// It is valid after Start().
|
||||
*Agent |
||||
} |
||||
|
||||
// NewTestAgent returns a started agent with the given name and
|
||||
// configuration. It panics if the agent could not be started. The
|
||||
// caller should call Shutdown() to stop the agent and remove temporary
|
||||
// directories.
|
||||
func NewTestAgent(name string, c *Config) *TestAgent { |
||||
a := &TestAgent{Name: name, Config: c} |
||||
a.Start() |
||||
return a |
||||
} |
||||
|
||||
type panicFailer struct{} |
||||
|
||||
func (f *panicFailer) Log(args ...interface{}) { fmt.Println(args...) } |
||||
func (f *panicFailer) FailNow() { panic("failed") } |
||||
|
||||
// Start starts a test agent. It panics if the agent could not be started.
|
||||
func (a *TestAgent) Start() *TestAgent { |
||||
if a.Agent != nil { |
||||
panic("TestAgent already started") |
||||
} |
||||
if a.Config == nil { |
||||
a.Config = TestConfig() |
||||
} |
||||
if a.Config.DataDir == "" { |
||||
name := "agent" |
||||
if a.Name != "" { |
||||
name = a.Name + "-agent" |
||||
} |
||||
name = strings.Replace(name, "/", "_", -1) |
||||
d, err := ioutil.TempDir(TempDir, name) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("Error creating data dir %s: %s", filepath.Join(TempDir, name), err)) |
||||
} |
||||
a.DataDir = d |
||||
a.Config.DataDir = d |
||||
} |
||||
if a.Config.DNSRecursor != "" { |
||||
a.Config.DNSRecursors = append(a.Config.DNSRecursors, a.Config.DNSRecursor) |
||||
} |
||||
if a.Key != "" { |
||||
writeKey := func(key, filename string) { |
||||
path := filepath.Join(a.Config.DataDir, filename) |
||||
if err := initKeyring(path, key); err != nil { |
||||
panic(fmt.Sprintf("Error creating keyring %s: %s", path, err)) |
||||
} |
||||
} |
||||
writeKey(a.Key, serfLANKeyring) |
||||
writeKey(a.Key, serfWANKeyring) |
||||
} |
||||
|
||||
agent, err := NewAgent(a.Config) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("Error creating agent: %s", err)) |
||||
} |
||||
a.Agent = agent |
||||
a.Agent.LogOutput = a.LogOutput |
||||
a.Agent.LogWriter = a.LogWriter |
||||
if err := a.Agent.Start(); err != nil { |
||||
panic(fmt.Sprintf("Error starting agent: %s", err)) |
||||
} |
||||
|
||||
var out structs.IndexedNodes |
||||
retry.Run(&panicFailer{}, func(r *retry.R) { |
||||
if len(a.httpServers) == 0 { |
||||
r.Fatal("waiting for server") |
||||
} |
||||
if a.Config.Bootstrap && a.Config.Server { |
||||
// Ensure we have a leader and a node registration.
|
||||
args := &structs.DCSpecificRequest{Datacenter: a.Config.Datacenter} |
||||
if err := a.RPC("Catalog.ListNodes", args, &out); err != nil { |
||||
r.Fatalf("Catalog.ListNodes failed: %v", err) |
||||
} |
||||
if !out.QueryMeta.KnownLeader { |
||||
r.Fatalf("No leader") |
||||
} |
||||
if out.Index == 0 { |
||||
r.Fatalf("Consul index is 0") |
||||
} |
||||
} else { |
||||
req, _ := http.NewRequest("GET", "/v1/agent/self", nil) |
||||
resp := httptest.NewRecorder() |
||||
_, err := a.httpServers[0].AgentSelf(resp, req) |
||||
if err != nil || resp.Code != 200 { |
||||
r.Fatal("failed OK respose", err) |
||||
} |
||||
} |
||||
}) |
||||
a.dns = a.dnsServers[0] |
||||
a.srv = a.httpServers[0] |
||||
return a |
||||
} |
||||
|
||||
// Shutdown stops the agent and removes the data directory if it is
|
||||
// managed by the test agent.
|
||||
func (a *TestAgent) Shutdown() error { |
||||
defer func() { |
||||
if a.DataDir != "" { |
||||
os.RemoveAll(a.DataDir) |
||||
} |
||||
}() |
||||
return a.Agent.Shutdown() |
||||
} |
||||
|
||||
func (a *TestAgent) consulConfig() *consul.Config { |
||||
c, err := a.Agent.consulConfig() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return c |
||||
} |
||||
|
||||
// nextPort is the next available set of ports for the various
|
||||
// endpoints. Using a random value between 1024 and 63000 does not
|
||||
// eliminate the chance for a port conflict for concurrent tests but
|
||||
// great reduces it close to zero with almost no overhead.
|
||||
var nextPort uint64 = uint64(rand.Int63n(63000)) + uint64(1024) |
||||
|
||||
// BoolTrue and BoolFalse exist to create a *bool value.
|
||||
var BoolTrue = true |
||||
var BoolFalse = false |
||||
|
||||
// TestConfig returns a unique default configuration for testing an
|
||||
// agent.
|
||||
func TestConfig() *Config { |
||||
nodeID, err := uuid.GenerateUUID() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
port := int(atomic.AddUint64(&nextPort, 10)) |
||||
|
||||
conf := DefaultConfig() |
||||
conf.Version = version.Version |
||||
conf.VersionPrerelease = "c.d" |
||||
conf.AdvertiseAddr = "127.0.0.1" |
||||
conf.Bootstrap = true |
||||
conf.Datacenter = "dc1" |
||||
conf.NodeName = fmt.Sprintf("Node %d", port) |
||||
conf.NodeID = types.NodeID(nodeID) |
||||
conf.BindAddr = "127.0.0.1" |
||||
conf.Ports.DNS = port + 1 |
||||
conf.Ports.HTTP = port + 2 |
||||
conf.Ports.SerfLan = port + 3 |
||||
conf.Ports.SerfWan = port + 4 |
||||
conf.Ports.Server = port + 5 |
||||
conf.Server = true |
||||
conf.ACLEnforceVersion8 = &BoolFalse |
||||
conf.ACLDatacenter = "dc1" |
||||
conf.ACLMasterToken = "root" |
||||
|
||||
cons := consul.DefaultConfig() |
||||
conf.ConsulConfig = cons |
||||
|
||||
cons.SerfLANConfig.MemberlistConfig.SuspicionMult = 3 |
||||
cons.SerfLANConfig.MemberlistConfig.ProbeTimeout = 100 * time.Millisecond |
||||
cons.SerfLANConfig.MemberlistConfig.ProbeInterval = 100 * time.Millisecond |
||||
cons.SerfLANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond |
||||
|
||||
cons.SerfWANConfig.MemberlistConfig.SuspicionMult = 3 |
||||
cons.SerfWANConfig.MemberlistConfig.ProbeTimeout = 100 * time.Millisecond |
||||
cons.SerfWANConfig.MemberlistConfig.ProbeInterval = 100 * time.Millisecond |
||||
cons.SerfWANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond |
||||
|
||||
cons.RaftConfig.LeaderLeaseTimeout = 20 * time.Millisecond |
||||
cons.RaftConfig.HeartbeatTimeout = 40 * time.Millisecond |
||||
cons.RaftConfig.ElectionTimeout = 40 * time.Millisecond |
||||
|
||||
cons.CoordinateUpdatePeriod = 100 * time.Millisecond |
||||
cons.ServerHealthInterval = 10 * time.Millisecond |
||||
return conf |
||||
} |
||||
|
||||
// TestACLConfig returns a default configuration for testing an agent
|
||||
// with ACLs.
|
||||
func TestACLConfig() *Config { |
||||
c := TestConfig() |
||||
c.ACLDatacenter = c.Datacenter |
||||
c.ACLDefaultPolicy = "deny" |
||||
c.ACLMasterToken = "root" |
||||
c.ACLAgentToken = "root" |
||||
c.ACLAgentMasterToken = "towel" |
||||
c.ACLEnforceVersion8 = &BoolTrue |
||||
return c |
||||
} |
Loading…
Reference in new issue