tests: ensure integration tests show logs from the containers to help debugging (#13593)

This commit is contained in:
R.B. Boyer 2022-06-24 10:26:17 -05:00 committed by GitHub
parent a3a4495e78
commit a1e911a70c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 143 additions and 55 deletions

View File

@ -333,12 +333,12 @@ ifeq ("$(GOTAGS)","")
@docker tag consul-dev:latest consul:local
@docker run --rm -t consul:local consul version
@cd ./test/integration/consul-container && \
go test -v -timeout=30m ./upgrade --target-version local --latest-version latest
go test -v -timeout=30m ./... --target-version local --latest-version latest
else
@docker tag consul-dev:latest hashicorp/consul-enterprise:local
@docker run --rm -t hashicorp/consul-enterprise:local consul version
@cd ./test/integration/consul-container && \
go test -v -timeout=30m ./upgrade --tags $(GOTAGS) --target-version local --latest-version latest
go test -v -timeout=30m ./... --tags $(GOTAGS) --target-version local --latest-version latest
endif
.PHONY: test-metrics-integ

View File

@ -7,6 +7,7 @@ require (
github.com/hashicorp/consul/api v1.11.0
github.com/hashicorp/consul/sdk v0.8.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/hcl v1.0.0
github.com/stretchr/testify v1.7.0
github.com/testcontainers/testcontainers-go v0.13.0
)

View File

@ -427,6 +427,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=

View File

@ -9,6 +9,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/hcl"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
@ -48,7 +49,6 @@ func newConsulContainerWithReq(ctx context.Context, req testcontainers.Container
// NewConsulContainer starts a Consul node in a container with the given config.
func NewConsulContainer(ctx context.Context, config Config) (Node, error) {
license, err := readLicense()
if err != nil {
return nil, err
@ -64,30 +64,29 @@ func NewConsulContainer(ctx context.Context, config Config) (Node, error) {
return nil, err
}
pc, err := readSomeConfigFileFields(config.HCL)
if err != nil {
return nil, err
}
configFile, err := createConfigFile(config.HCL)
if err != nil {
return nil, err
}
skipReaper := isRYUKDisabled()
req := testcontainers.ContainerRequest{
Image: consulImage + ":" + config.Version,
ExposedPorts: []string{"8500/tcp"},
WaitingFor: wait.ForLog(bootLogLine).WithStartupTimeout(10 * time.Second),
AutoRemove: false,
Name: name,
Mounts: testcontainers.ContainerMounts{
testcontainers.ContainerMount{Source: testcontainers.DockerBindMountSource{HostPath: configFile}, Target: "/consul/config/config.hcl"},
testcontainers.ContainerMount{Source: testcontainers.DockerBindMountSource{HostPath: tmpDirData}, Target: "/consul/data"},
},
Cmd: config.Cmd,
SkipReaper: skipReaper,
Env: map[string]string{"CONSUL_LICENSE": license},
}
req := newContainerRequest(config, name, configFile, tmpDirData, license)
container, err := newConsulContainerWithReq(ctx, req)
if err != nil {
return nil, err
}
if err := container.StartLogProducer(ctx); err != nil {
return nil, err
}
container.FollowOutput(&NodeLogConsumer{
Prefix: pc.NodeName,
})
localIP, err := container.Host(ctx)
if err != nil {
return nil, err
@ -104,21 +103,42 @@ func NewConsulContainer(ctx context.Context, config Config) (Node, error) {
}
uri := fmt.Sprintf("http://%s:%s", localIP, mappedPort.Port())
c := new(consulContainerNode)
c.config = config
c.container = container
c.ip = ip
c.port = mappedPort.Int()
apiConfig := api.DefaultConfig()
apiConfig.Address = uri
c.client, err = api.NewClient(apiConfig)
c.ctx = ctx
c.req = req
c.dataDir = tmpDirData
apiClient, err := api.NewClient(apiConfig)
if err != nil {
return nil, err
}
return c, nil
return &consulContainerNode{
config: config,
container: container,
ip: ip,
port: mappedPort.Int(),
client: apiClient,
ctx: ctx,
req: req,
dataDir: tmpDirData,
}, nil
}
func newContainerRequest(config Config, name, configFile, dataDir, license string) testcontainers.ContainerRequest {
skipReaper := isRYUKDisabled()
return testcontainers.ContainerRequest{
Image: consulImage + ":" + config.Version,
ExposedPorts: []string{"8500/tcp"},
WaitingFor: wait.ForLog(bootLogLine).WithStartupTimeout(10 * time.Second),
AutoRemove: false,
Name: name,
Mounts: []testcontainers.ContainerMount{
{Source: testcontainers.DockerBindMountSource{HostPath: configFile}, Target: "/consul/config/config.hcl"},
{Source: testcontainers.DockerBindMountSource{HostPath: dataDir}, Target: "/consul/data"},
},
Cmd: config.Cmd,
SkipReaper: skipReaper,
Env: map[string]string{"CONSUL_LICENSE": license},
}
}
// GetClient returns an API client that can be used to communicate with the Node.
@ -132,25 +152,44 @@ func (c *consulContainerNode) GetAddr() (string, int) {
}
func (c *consulContainerNode) Upgrade(ctx context.Context, config Config) error {
pc, err := readSomeConfigFileFields(config.HCL)
if err != nil {
return err
}
file, err := createConfigFile(config.HCL)
if err != nil {
return err
}
c.req.Cmd = config.Cmd
c.req.Mounts = testcontainers.ContainerMounts{
testcontainers.ContainerMount{Source: testcontainers.DockerBindMountSource{HostPath: file}, Target: "/consul/config/config.hcl"},
testcontainers.ContainerMount{Source: testcontainers.DockerBindMountSource{HostPath: c.dataDir}, Target: "/consul/data"},
}
c.req.Image = consulImage + ":" + config.Version
err = c.container.Terminate(ctx)
if err != nil {
req2 := newContainerRequest(
config,
c.req.Name,
file,
c.dataDir,
"",
)
req2.Env = c.req.Env // copy license
_ = c.container.StopLogProducer()
if err := c.container.Terminate(ctx); err != nil {
return err
}
c.req = req2
container, err := newConsulContainerWithReq(ctx, c.req)
if err != nil {
return err
}
if err := container.StartLogProducer(ctx); err != nil {
return err
}
container.FollowOutput(&NodeLogConsumer{
Prefix: pc.NodeName,
})
c.container = container
localIP, err := container.Host(ctx)
@ -185,7 +224,19 @@ func (c *consulContainerNode) Upgrade(ctx context.Context, config Config) error
// Terminate attempts to terminate the container. On failure, an error will be
// returned and the reaper process (RYUK) will handle cleanup.
func (c *consulContainerNode) Terminate() error {
return c.container.Terminate(c.ctx)
if c.container == nil {
return nil
}
err := c.container.StopLogProducer()
if err1 := c.container.Terminate(c.ctx); err == nil {
err = err1
}
c.container = nil
return err
}
// isRYUKDisabled returns whether the reaper process (RYUK) has been disabled
@ -236,3 +287,15 @@ func createConfigFile(HCL string) (string, error) {
}
return configFile, nil
}
type parsedConfig struct {
NodeName string `hcl:"node_name"`
}
func readSomeConfigFileFields(HCL string) (parsedConfig, error) {
var pc parsedConfig
if err := hcl.Decode(&pc, HCL); err != nil {
return pc, fmt.Errorf("Failed to parse config file: %w", err)
}
return pc, nil
}

View File

@ -0,0 +1,23 @@
package node
import (
"fmt"
"os"
"github.com/testcontainers/testcontainers-go"
)
type NodeLogConsumer struct {
Prefix string
}
var _ testcontainers.LogConsumer = (*NodeLogConsumer)(nil)
func (c *NodeLogConsumer) Accept(log testcontainers.Log) {
switch log.LogType {
case "STDOUT":
fmt.Fprint(os.Stdout, c.Prefix+" ~~ "+string(log.Content))
case "STDERR":
fmt.Fprint(os.Stderr, c.Prefix+" ~~ "+string(log.Content))
}
}

View File

@ -2,19 +2,18 @@ package node
import (
"context"
"github.com/hashicorp/consul/api"
)
type (
// Node represent a Consul node abstraction
Node interface {
Terminate() error
GetClient() *api.Client
GetAddr() (string, int)
GetConfig() Config
Upgrade(ctx context.Context, config Config) error
}
)
// Node represent a Consul node abstraction
type Node interface {
Terminate() error
GetClient() *api.Client
GetAddr() (string, int)
GetConfig() Config
Upgrade(ctx context.Context, config Config) error
}
// Config is a set of configurations required to create a Node
type Config struct {

View File

@ -23,7 +23,7 @@ func TestLeadershipMetrics(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
server=true
telemetry {
statsite_address = "127.0.0.1:2180"
@ -37,7 +37,7 @@ func TestLeadershipMetrics(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
bootstrap_expect=3
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/api"
libcluster "github.com/hashicorp/consul/integration/consul-container/libs/cluster"
"github.com/hashicorp/consul/integration/consul-container/libs/node"
"github.com/hashicorp/consul/integration/consul-container/libs/utils"
@ -74,7 +75,7 @@ func TestMixedServersMajorityLatestGAClient(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},
Version: *utils.TargetImage,
@ -84,7 +85,7 @@ func TestMixedServersMajorityLatestGAClient(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
bootstrap_expect=3
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},
@ -151,7 +152,7 @@ func TestMixedServersMajorityTargetGAClient(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
bootstrap_expect=3
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},
@ -162,7 +163,7 @@ func TestMixedServersMajorityTargetGAClient(t *testing.T) {
configs = append(configs,
node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},
Version: *utils.LatestImage,
@ -227,7 +228,7 @@ func clientsCreate(t *testing.T, numClients int, version string, serfKey string)
node.Config{
HCL: fmt.Sprintf(`
node_name = %q
log_level = "TRACE"
log_level = "DEBUG"
encrypt = %q`, utils.RandName("consul-client"), serfKey),
Cmd: []string{"agent", "-client=0.0.0.0"},
Version: version,
@ -255,7 +256,7 @@ func serversCluster(t *testing.T, numServers int, version string) *libcluster.Cl
for i := 0; i < numServers; i++ {
configs = append(configs, node.Config{
HCL: `node_name="` + utils.RandName("consul-server") + `"
log_level="TRACE"
log_level="DEBUG"
bootstrap_expect=3
server=true`,
Cmd: []string{"agent", "-client=0.0.0.0"},