You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/testing/deployer/sprawl/sprawltest/sprawltest.go

219 lines
6.2 KiB

[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
1 year ago
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package sprawltest
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/testing/deployer/sprawl"
"github.com/hashicorp/consul/testing/deployer/sprawl/internal/runner"
"github.com/hashicorp/consul/testing/deployer/topology"
)
// TODO(rb): move comments to doc.go
var (
// set SPRAWL_WORKDIR_ROOT in the environment to have the test output
// coalesced in here. By default it uses a directory called "workdir" in
// each package.
workdirRoot string
// set SPRAWL_KEEP_WORKDIR=1 in the environment to keep the workdir output
// intact. Files are all destroyed by default.
keepWorkdirOnFail bool
// set SPRAWL_KEEP_RUNNING=1 in the environment to keep the workdir output
// intact and also refrain from tearing anything down. Things are all
// destroyed by default.
//
// SPRAWL_KEEP_RUNNING=1 implies SPRAWL_KEEP_WORKDIR=1
keepRunningOnFail bool
// set SPRAWL_SKIP_OLD_CLEANUP to prevent the library from tearing down and
// removing anything found in the working directory at init time. The
// default behavior is to do this.
skipOldCleanup bool
)
var cleanupPriorRunOnce sync.Once
func init() {
if root := os.Getenv("SPRAWL_WORKDIR_ROOT"); root != "" {
fmt.Fprintf(os.Stdout, "INFO: sprawltest: SPRAWL_WORKDIR_ROOT set; using %q as output root\n", root)
workdirRoot = root
} else {
workdirRoot = "workdir"
}
if os.Getenv("SPRAWL_KEEP_WORKDIR") == "1" {
keepWorkdirOnFail = true
fmt.Fprintf(os.Stdout, "INFO: sprawltest: SPRAWL_KEEP_WORKDIR set; not destroying workdir on failure\n")
}
if os.Getenv("SPRAWL_KEEP_RUNNING") == "1" {
keepRunningOnFail = true
keepWorkdirOnFail = true
fmt.Fprintf(os.Stdout, "INFO: sprawltest: SPRAWL_KEEP_RUNNING set; not tearing down resources on failure\n")
}
if os.Getenv("SPRAWL_SKIP_OLD_CLEANUP") == "1" {
skipOldCleanup = true
fmt.Fprintf(os.Stdout, "INFO: sprawltest: SPRAWL_SKIP_OLD_CLEANUP set; not cleaning up anything found in %q\n", workdirRoot)
}
if !skipOldCleanup {
cleanupPriorRunOnce.Do(func() {
fmt.Fprintf(os.Stdout, "INFO: sprawltest: triggering cleanup of any prior test runs\n")
CleanupWorkingDirectories()
})
}
}
// Launch will create the topology defined by the provided configuration and
// bring up all of the relevant clusters.
//
// - Logs will be routed to (*testing.T).Logf.
//
// - By default everything will be stopped and removed via
// (*testing.T).Cleanup. For failed tests, this can be skipped by setting the
// environment variable SKIP_TEARDOWN=1.
func Launch(t *testing.T, cfg *topology.Config) *sprawl.Sprawl {
SkipIfTerraformNotPresent(t)
logger := testutil.Logger(t)
// IMO default level for tests should be info, not warn
logger.SetLevel(testutil.TestLogLevelWithDefault(hclog.Info))
sp, err := sprawl.Launch(
logger,
initWorkingDirectory(t),
cfg,
)
require.NoError(t, err)
stopOnCleanup(t, sp)
return sp
}
func initWorkingDirectory(t *testing.T) string {
// TODO(rb): figure out how to get the calling package which we can put in
// the middle here, which is likely 2 call frames away so maybe
// runtime.Callers can help
scratchDir := filepath.Join(workdirRoot, t.Name())
_ = os.RemoveAll(scratchDir) // cleanup prior runs
if err := os.MkdirAll(scratchDir, 0755); err != nil {
t.Fatalf("error: %v", err)
}
t.Cleanup(func() {
if t.Failed() && keepWorkdirOnFail {
t.Logf("test failed; leaving sprawl terraform definitions in: %s", scratchDir)
} else {
_ = os.RemoveAll(scratchDir)
}
})
return scratchDir
}
func stopOnCleanup(t *testing.T, sp *sprawl.Sprawl) {
t.Cleanup(func() {
if t.Failed() && keepWorkdirOnFail {
// It's only worth it to capture the logs if we aren't going to
// immediately discard them.
if err := sp.CaptureLogs(context.Background()); err != nil {
t.Logf("log capture encountered failures: %v", err)
}
if err := sp.SnapshotEnvoy(context.Background()); err != nil {
t.Logf("envoy snapshot capture encountered failures: %v", err)
}
}
if t.Failed() && keepRunningOnFail {
t.Log("test failed; leaving sprawl running")
} else {
//nolint:errcheck
sp.Stop()
}
})
}
// CleanupWorkingDirectories is meant to run in an init() once at the start of
// any tests.
func CleanupWorkingDirectories() {
fi, err := os.ReadDir(workdirRoot)
if os.IsNotExist(err) {
return
} else if err != nil {
fmt.Fprintf(os.Stderr, "WARN: sprawltest: unable to scan 'workdir' for prior runs to cleanup\n")
return
} else if len(fi) == 0 {
fmt.Fprintf(os.Stdout, "INFO: sprawltest: no prior tests to clean up\n")
return
}
r, err := runner.Load(hclog.NewNullLogger())
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: sprawltest: unable to look for 'terraform' and 'docker' binaries\n")
return
}
ctx := context.Background()
for _, d := range fi {
if !d.IsDir() {
continue
}
path := filepath.Join(workdirRoot, d.Name(), "terraform")
fmt.Fprintf(os.Stdout, "INFO: sprawltest: cleaning up failed prior run in: %s\n", path)
err := r.TerraformExec(ctx, []string{
"init", "-input=false",
}, io.Discard, path)
err2 := r.TerraformExec(ctx, []string{
"destroy", "-input=false", "-auto-approve", "-refresh=false",
}, io.Discard, path)
if err2 != nil {
err = multierror.Append(err, err2)
}
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: sprawltest: could not clean up failed prior run in: %s: %v\n", path, err)
} else {
_ = os.RemoveAll(path)
}
}
}
func SkipIfTerraformNotPresent(t *testing.T) {
const terraformBinaryName = "terraform"
path, err := exec.LookPath(terraformBinaryName)
if err != nil || path == "" {
t.Skipf("%q not found on $PATH - download and install to run this test", terraformBinaryName)
}
}
func MustSetResourceData(t *testing.T, res *pbresource.Resource, data proto.Message) *pbresource.Resource {
anyData, err := anypb.New(data)
require.NoError(t, err)
res.Data = anyData
return res
}