// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 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.SliceSetEq([]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.SliceSetEq(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") }) }