open-nomad/e2e/consul/consul.go
Tim Gross 9f05d62338
E2E with HCP Consul/Vault (#12267)
Use HCP Consul and HCP Vault for the Consul and Vault clusters used in E2E testing. This has the following benefits:

* Without the need to support mTLS bootstrapping for Consul and Vault, we can simplify the mTLS configuration by leaning on Terraform instead of janky bash shell scripting.
* Vault bootstrapping is no longer required, so we can eliminate even more janky shell scripting
* Our E2E exercises HCP, which is important to us as an organization
* With the reduction in configurability, we can simplify the Terraform configuration and drop the complicated `provision.sh`/`provision.ps1` scripts we were using previously. We can template Nomad configuration files and upload them with the `file` provisioner.
* Packer builds for Linux and Windows become much simpler.

tl;dr way less janky shell scripting!
2022-03-18 09:27:28 -04:00

261 lines
7.8 KiB
Go

package consul
import (
"fmt"
"os"
api "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/e2e/e2eutil"
"github.com/hashicorp/nomad/e2e/framework"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
)
const (
consulJobBasic = "consul/input/consul_example.nomad"
consulJobCanaryTags = "consul/input/canary_tags.nomad"
consulJobRegisterOnUpdatePart1 = "consul/input/services_empty.nomad"
consulJobRegisterOnUpdatePart2 = "consul/input/services_present.nomad"
)
const (
// unless otherwise set, tests should just use the default consul namespace
consulNamespace = "default"
)
type ConsulE2ETest struct {
framework.TC
jobIds []string
}
func init() {
framework.AddSuites(&framework.TestSuite{
Component: "Consul",
CanRunLocal: true,
Consul: true,
Cases: []framework.TestCase{
new(ConsulE2ETest),
new(ScriptChecksE2ETest),
new(CheckRestartE2ETest),
new(OnUpdateChecksTest),
},
})
}
func (tc *ConsulE2ETest) BeforeAll(f *framework.F) {
e2eutil.WaitForLeader(f.T(), tc.Nomad())
e2eutil.WaitForNodesReady(f.T(), tc.Nomad(), 1)
}
func (tc *ConsulE2ETest) AfterEach(f *framework.F) {
if os.Getenv("NOMAD_TEST_SKIPCLEANUP") == "1" {
return
}
for _, id := range tc.jobIds {
_, _, err := tc.Nomad().Jobs().Deregister(id, true, nil)
require.NoError(f.T(), err)
}
tc.jobIds = []string{}
require.NoError(f.T(), tc.Nomad().System().GarbageCollect())
}
// TestConsulRegistration asserts that a job registers services with tags in Consul.
func (tc *ConsulE2ETest) TestConsulRegistration(f *framework.F) {
t := f.T()
r := require.New(t)
nomadClient := tc.Nomad()
jobId := "consul" + uuid.Short()
tc.jobIds = append(tc.jobIds, jobId)
allocations := e2eutil.RegisterAndWaitForAllocs(f.T(), nomadClient, consulJobBasic, jobId, "")
require.Equal(t, 3, len(allocations))
allocIDs := e2eutil.AllocIDsFromAllocationListStubs(allocations)
e2eutil.WaitForAllocsRunning(t, tc.Nomad(), allocIDs)
expectedTags := []string{
"cache",
"global",
}
// Assert services get registered
e2eutil.RequireConsulRegistered(r, tc.Consul(), consulNamespace, "consul-example", 3)
services, _, err := tc.Consul().Catalog().Service("consul-example", "", nil)
require.NoError(t, err)
for _, s := range services {
// If we've made it this far the tags should *always* match
require.ElementsMatch(t, expectedTags, s.ServiceTags)
}
// Stop the job
e2eutil.WaitForJobStopped(t, nomadClient, jobId)
// Verify that services were de-registered in Consul
e2eutil.RequireConsulDeregistered(r, tc.Consul(), consulNamespace, "consul-example")
}
func (tc *ConsulE2ETest) TestConsulRegisterOnUpdate(f *framework.F) {
t := f.T()
r := require.New(t)
nomadClient := tc.Nomad()
catalog := tc.Consul().Catalog()
jobID := "consul" + uuid.Short()
tc.jobIds = append(tc.jobIds, jobID)
// Initial job has no services for task.
allocations := e2eutil.RegisterAndWaitForAllocs(t, nomadClient, consulJobRegisterOnUpdatePart1, jobID, "")
require.Equal(t, 1, len(allocations))
allocIDs := e2eutil.AllocIDsFromAllocationListStubs(allocations)
e2eutil.WaitForAllocsRunning(t, tc.Nomad(), allocIDs)
// Assert service not yet registered.
results, _, err := catalog.Service("nc-service", "", nil)
require.NoError(t, err)
require.Empty(t, results)
// On update, add services for task.
allocations = e2eutil.RegisterAndWaitForAllocs(t, nomadClient, consulJobRegisterOnUpdatePart2, jobID, "")
require.Equal(t, 1, len(allocations))
allocIDs = e2eutil.AllocIDsFromAllocationListStubs(allocations)
e2eutil.WaitForAllocsRunning(t, tc.Nomad(), allocIDs)
// Assert service is now registered.
e2eutil.RequireConsulRegistered(r, tc.Consul(), consulNamespace, "nc-service", 1)
}
// TestCanaryInplaceUpgrades verifies setting and unsetting canary tags
func (tc *ConsulE2ETest) TestCanaryInplaceUpgrades(f *framework.F) {
t := f.T()
// TODO(shoenig) https://github.com/hashicorp/nomad/issues/9627
t.Skip("THIS TEST IS BROKEN (#9627)")
nomadClient := tc.Nomad()
consulClient := tc.Consul()
jobId := "consul" + uuid.Generate()[0:8]
tc.jobIds = append(tc.jobIds, jobId)
allocs := e2eutil.RegisterAndWaitForAllocs(f.T(), nomadClient, consulJobCanaryTags, jobId, "")
require.Equal(t, 2, len(allocs))
allocIDs := e2eutil.AllocIDsFromAllocationListStubs(allocs)
e2eutil.WaitForAllocsRunning(t, nomadClient, allocIDs)
// Start a deployment
job, _, err := nomadClient.Jobs().Info(jobId, nil)
require.NoError(t, err)
job.Meta = map[string]string{"version": "2"}
resp, _, err := nomadClient.Jobs().Register(job, nil)
require.NoError(t, err)
require.NotEmpty(t, resp.EvalID)
// Eventually have a canary
var activeDeploy *api.Deployment
testutil.WaitForResult(func() (bool, error) {
deploys, _, err := nomadClient.Jobs().Deployments(jobId, false, nil)
if err != nil {
return false, err
}
if expected := 2; len(deploys) != expected {
return false, fmt.Errorf("expected 2 deploys but found %v", deploys)
}
for _, d := range deploys {
if d.Status == structs.DeploymentStatusRunning {
activeDeploy = d
break
}
}
if activeDeploy == nil {
return false, fmt.Errorf("no running deployments: %v", deploys)
}
if expected := 1; len(activeDeploy.TaskGroups["consul_canary_test"].PlacedCanaries) != expected {
return false, fmt.Errorf("expected %d placed canaries but found %#v",
expected, activeDeploy.TaskGroups["consul_canary_test"])
}
return true, nil
}, func(err error) {
f.NoError(err, "error while waiting for deploys")
})
allocID := activeDeploy.TaskGroups["consul_canary_test"].PlacedCanaries[0]
testutil.WaitForResult(func() (bool, error) {
alloc, _, err := nomadClient.Allocations().Info(allocID, nil)
if err != nil {
return false, err
}
if alloc.DeploymentStatus == nil {
return false, fmt.Errorf("canary alloc %s has no deployment status", allocID)
}
if alloc.DeploymentStatus.Healthy == nil {
return false, fmt.Errorf("canary alloc %s has no deployment health: %#v",
allocID, alloc.DeploymentStatus)
}
return *alloc.DeploymentStatus.Healthy, fmt.Errorf("expected healthy canary but found: %#v",
alloc.DeploymentStatus)
}, func(err error) {
f.NoError(err, "error waiting for canary to be healthy")
})
// Check Consul for canary tags
testutil.WaitForResult(func() (bool, error) {
consulServices, _, err := consulClient.Catalog().Service("canarytest", "", nil)
if err != nil {
return false, err
}
for _, s := range consulServices {
if helper.CompareSliceSetString([]string{"canary", "foo"}, s.ServiceTags) {
return true, nil
}
}
return false, fmt.Errorf(`could not find service tags {"canary", "foo"}: %#v`, consulServices)
}, func(err error) {
f.NoError(err, "error waiting for canary tags")
})
// Promote canary
{
resp, _, err := nomadClient.Deployments().PromoteAll(activeDeploy.ID, nil)
require.NoError(t, err)
require.NotEmpty(t, resp.EvalID)
}
// Eventually canary is promoted
testutil.WaitForResult(func() (bool, error) {
alloc, _, err := nomadClient.Allocations().Info(allocID, nil)
if err != nil {
return false, err
}
return !alloc.DeploymentStatus.Canary, fmt.Errorf("still a canary")
}, func(err error) {
require.NoError(t, err, "error waiting for canary to be promoted")
})
// Verify that no instances have canary tags
expected := []string{"foo", "bar"}
testutil.WaitForResult(func() (bool, error) {
consulServices, _, err := consulClient.Catalog().Service("canarytest", "", nil)
if err != nil {
return false, err
}
for _, s := range consulServices {
if !helper.CompareSliceSetString(expected, s.ServiceTags) {
return false, fmt.Errorf("expected %#v Consul tags but found %#v",
expected, s.ServiceTags)
}
}
return true, nil
}, func(err error) {
require.NoError(t, err, "error waiting for non-canary tags")
})
}