open-nomad/e2e/spread/spread_test.go
James Rasell 90d0b9157f
e2e: rewrite spread suite to use new e2e style. (#14598)
The rewrite refactors the suite to use the new style along with
other recent testing improvements. In order to ensure the spread
tests do not impact each other, there is new cleanup functionality
to ensure both the job and allocations are removed from state
before the test exits completely.
2022-09-15 17:12:20 +02:00

143 lines
4.4 KiB
Go

package spread
import (
"fmt"
"strings"
"testing"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/e2e/e2eutil"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/assert"
)
const (
evenJobFilePath = "./input/even_spread.nomad"
multipleJobFilePath = "./input/multiple_spread.nomad"
)
func TestSpread(t *testing.T) {
nomadClient := e2eutil.NomadClient(t)
e2eutil.WaitForLeader(t, nomadClient)
e2eutil.WaitForNodesReady(t, nomadClient, 4)
// Run our test cases.
t.Run("TestSpread_Even", testSpreadEven)
t.Run("TestSpread_Multiple", testSpreadMultiple)
}
func testSpreadEven(t *testing.T) {
nomadClient := e2eutil.NomadClient(t)
// Generate a job ID and register the test job.
jobID := "spread-" + uuid.Short()
allocs := e2eutil.RegisterAndWaitForAllocs(t, nomadClient, evenJobFilePath, jobID, "")
// Ensure the test cleans its own job and allocations fully, so it does not
// impact other spread tests.
t.Cleanup(func() { cleanupJob(t, nomadClient, jobID, allocs) })
dcToAllocs := make(map[string]int)
for _, allocStub := range allocs {
alloc, _, err := nomadClient.Allocations().Info(allocStub.ID, nil)
must.NoError(t, err)
must.Greater(t, len(alloc.Metrics.ScoreMetaData), 0)
node, _, err := nomadClient.Nodes().Info(alloc.NodeID, nil)
must.NoError(t, err)
dcToAllocs[node.Datacenter]++
}
must.Eq(t, map[string]int{"dc1": 3, "dc2": 3}, dcToAllocs)
}
func testSpreadMultiple(t *testing.T) {
nomadClient := e2eutil.NomadClient(t)
// Generate a job ID and register the test job.
jobID := "spread-" + uuid.Short()
allocs := e2eutil.RegisterAndWaitForAllocs(t, nomadClient, multipleJobFilePath, jobID, "")
// Ensure the test cleans its own job and allocations fully, so it does not
// impact other spread tests.
t.Cleanup(func() { cleanupJob(t, nomadClient, jobID, allocs) })
// Verify spread score and alloc distribution
dcToAllocs := make(map[string]int)
rackToAllocs := make(map[string]int)
allocMetrics := make(map[string]*api.AllocationMetric)
for _, allocStub := range allocs {
alloc, _, err := nomadClient.Allocations().Info(allocStub.ID, nil)
must.NoError(t, err)
must.Greater(t, len(alloc.Metrics.ScoreMetaData), 0)
allocMetrics[allocStub.ID] = alloc.Metrics
node, _, err := nomadClient.Nodes().Info(alloc.NodeID, nil)
must.NoError(t, err)
dcToAllocs[node.Datacenter]++
if rack := node.Meta["rack"]; rack != "" {
rackToAllocs[rack]++
}
}
failureReport := report(allocMetrics)
must.Eq(t, map[string]int{"dc1": 5, "dc2": 5}, dcToAllocs, failureReport)
must.Eq(t, map[string]int{"r1": 7, "r2": 3}, rackToAllocs, failureReport)
}
func cleanupJob(t *testing.T, nomadClient *api.Client, jobID string, allocs []*api.AllocationListStub) {
_, _, err := nomadClient.Jobs().Deregister(jobID, true, nil)
assert.NoError(t, err)
// Ensure that all allocations have been removed from state. This is an
// important aspect of the cleaning required which allows the spread
// test to run successfully.
assert.Eventually(t, func() bool {
// Run the garbage collector to remove all terminal allocations.
must.NoError(t, nomadClient.System().GarbageCollect())
for _, allocStub := range allocs {
_, _, err := nomadClient.Allocations().Info(allocStub.ID, nil)
if err == nil {
return false
} else {
if !strings.Contains(err.Error(), "alloc not found") {
return false
}
}
}
return true
}, 10*time.Second, 200*time.Millisecond)
}
func report(metrics map[string]*api.AllocationMetric) must.PostScript {
var s strings.Builder
for allocID, m := range metrics {
s.WriteString("Alloc ID: " + allocID + "\n")
s.WriteString(fmt.Sprintf(" NodesEvaluated: %d\n", m.NodesEvaluated))
s.WriteString(fmt.Sprintf(" NodesAvailable: %#v\n", m.NodesAvailable))
s.WriteString(fmt.Sprintf(" ClassFiltered: %#v\n", m.ClassFiltered))
s.WriteString(fmt.Sprintf(" ConstraintFiltered: %#v\n", m.ConstraintFiltered))
s.WriteString(fmt.Sprintf(" NodesExhausted: %d\n", m.NodesExhausted))
s.WriteString(fmt.Sprintf(" ClassExhausted: %#v\n", m.ClassExhausted))
s.WriteString(fmt.Sprintf(" DimensionExhausted: %#v\n", m.DimensionExhausted))
s.WriteString(fmt.Sprintf(" QuotaExhausted: %#v\n", m.QuotaExhausted))
for _, nodeMeta := range m.ScoreMetaData {
s.WriteString(fmt.Sprintf(" NodeID: %s, NormScore: %f, Scores: %#v\n",
nodeMeta.NodeID, nodeMeta.NormScore, nodeMeta.Scores))
}
}
return must.Sprint(s.String())
}