mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 00:15:43 +03:00
* jobspec: add a chown option to artifact block This PR adds a boolean 'chown' field to the artifact block. It indicates whether the Nomad client should chown the downloaded files and directories to be owned by the task.user. This is useful for drivers like raw_exec and exec2 which are subject to the host filesystem user permissions structure. Before, these drivers might not be able to use or manage the downloaded artifacts since they would be owned by the root user on a typical Nomad client configuration. * api: no need for pointer of chown field
929 lines
21 KiB
Go
929 lines
21 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/api/internal/testutil"
|
|
"github.com/shoenig/test/must"
|
|
)
|
|
|
|
func TestTaskGroup_NewTaskGroup(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 2)
|
|
expect := &TaskGroup{
|
|
Name: pointerOf("grp1"),
|
|
Count: pointerOf(2),
|
|
}
|
|
must.Eq(t, expect, grp)
|
|
}
|
|
|
|
func TestTaskGroup_Constrain(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 1)
|
|
|
|
// Add a constraint to the group
|
|
out := grp.Constrain(NewConstraint("kernel.name", "=", "darwin"))
|
|
must.Len(t, 1, grp.Constraints)
|
|
|
|
// Check that the group was returned
|
|
must.Eq(t, grp, out)
|
|
|
|
// Add a second constraint
|
|
grp.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000"))
|
|
expect := []*Constraint{
|
|
{
|
|
LTarget: "kernel.name",
|
|
RTarget: "darwin",
|
|
Operand: "=",
|
|
},
|
|
{
|
|
LTarget: "memory.totalbytes",
|
|
RTarget: "128000000",
|
|
Operand: ">=",
|
|
},
|
|
}
|
|
must.Eq(t, expect, grp.Constraints)
|
|
}
|
|
|
|
func TestTaskGroup_AddAffinity(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 1)
|
|
|
|
// Add an affinity to the group
|
|
out := grp.AddAffinity(NewAffinity("kernel.version", "=", "4.6", 100))
|
|
must.Len(t, 1, grp.Affinities)
|
|
|
|
// Check that the group was returned
|
|
must.Eq(t, grp, out)
|
|
|
|
// Add a second affinity
|
|
grp.AddAffinity(NewAffinity("${node.affinity}", "=", "dc2", 50))
|
|
expect := []*Affinity{
|
|
{
|
|
LTarget: "kernel.version",
|
|
RTarget: "4.6",
|
|
Operand: "=",
|
|
Weight: pointerOf(int8(100)),
|
|
},
|
|
{
|
|
LTarget: "${node.affinity}",
|
|
RTarget: "dc2",
|
|
Operand: "=",
|
|
Weight: pointerOf(int8(50)),
|
|
},
|
|
}
|
|
must.Eq(t, expect, grp.Affinities)
|
|
}
|
|
|
|
func TestTaskGroup_SetMeta(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 1)
|
|
|
|
// Initializes an empty map
|
|
out := grp.SetMeta("foo", "bar")
|
|
must.NotNil(t, grp.Meta)
|
|
|
|
// Check that we returned the group
|
|
must.Eq(t, grp, out)
|
|
|
|
// Add a second meta k/v
|
|
grp.SetMeta("baz", "zip")
|
|
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
|
must.Eq(t, expect, grp.Meta)
|
|
}
|
|
|
|
func TestTaskGroup_AddSpread(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 1)
|
|
|
|
// Create and add spread
|
|
spreadTarget := NewSpreadTarget("r1", 50)
|
|
spread := NewSpread("${meta.rack}", 100, []*SpreadTarget{spreadTarget})
|
|
|
|
out := grp.AddSpread(spread)
|
|
must.Len(t, 1, grp.Spreads)
|
|
|
|
// Check that the group was returned
|
|
must.Eq(t, grp, out)
|
|
|
|
// Add a second spread
|
|
spreadTarget2 := NewSpreadTarget("dc1", 100)
|
|
spread2 := NewSpread("${node.datacenter}", 100, []*SpreadTarget{spreadTarget2})
|
|
|
|
grp.AddSpread(spread2)
|
|
|
|
expect := []*Spread{
|
|
{
|
|
Attribute: "${meta.rack}",
|
|
Weight: pointerOf(int8(100)),
|
|
SpreadTarget: []*SpreadTarget{
|
|
{
|
|
Value: "r1",
|
|
Percent: 50,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Attribute: "${node.datacenter}",
|
|
Weight: pointerOf(int8(100)),
|
|
SpreadTarget: []*SpreadTarget{
|
|
{
|
|
Value: "dc1",
|
|
Percent: 100,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
must.Eq(t, expect, grp.Spreads)
|
|
}
|
|
|
|
func TestTaskGroup_AddTask(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
grp := NewTaskGroup("grp1", 1)
|
|
|
|
// Add the task to the task group
|
|
out := grp.AddTask(NewTask("task1", "java"))
|
|
must.Len(t, 1, out.Tasks)
|
|
|
|
// Check that we returned the group
|
|
must.Eq(t, grp, out)
|
|
|
|
// Add a second task
|
|
grp.AddTask(NewTask("task2", "exec"))
|
|
expect := []*Task{
|
|
{
|
|
Name: "task1",
|
|
Driver: "java",
|
|
},
|
|
{
|
|
Name: "task2",
|
|
Driver: "exec",
|
|
},
|
|
}
|
|
must.Eq(t, expect, grp.Tasks)
|
|
}
|
|
|
|
func TestTask_NewTask(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
expect := &Task{
|
|
Name: "task1",
|
|
Driver: "exec",
|
|
}
|
|
must.Eq(t, expect, task)
|
|
}
|
|
|
|
func TestTask_SetConfig(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
|
|
// Initializes an empty map
|
|
out := task.SetConfig("foo", "bar")
|
|
must.NotNil(t, task.Config)
|
|
|
|
// Check that we returned the task
|
|
must.Eq(t, task, out)
|
|
|
|
// Set another config value
|
|
task.SetConfig("baz", "zip")
|
|
expect := map[string]interface{}{"foo": "bar", "baz": "zip"}
|
|
must.Eq(t, expect, task.Config)
|
|
}
|
|
|
|
func TestTask_SetMeta(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
|
|
// Initializes an empty map
|
|
out := task.SetMeta("foo", "bar")
|
|
must.NotNil(t, out)
|
|
|
|
// Check that we returned the task
|
|
must.Eq(t, task, out)
|
|
|
|
// Set another meta k/v
|
|
task.SetMeta("baz", "zip")
|
|
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
|
must.Eq(t, expect, task.Meta)
|
|
}
|
|
|
|
func TestTask_Require(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
|
|
// Create some require resources
|
|
resources := &Resources{
|
|
CPU: pointerOf(1250),
|
|
MemoryMB: pointerOf(128),
|
|
DiskMB: pointerOf(2048),
|
|
Networks: []*NetworkResource{
|
|
{
|
|
CIDR: "0.0.0.0/0",
|
|
MBits: pointerOf(100),
|
|
ReservedPorts: []Port{{Label: "", Value: 80}, {Label: "", Value: 443}},
|
|
},
|
|
},
|
|
}
|
|
out := task.Require(resources)
|
|
must.Eq(t, resources, task.Resources)
|
|
|
|
// Check that we returned the task
|
|
must.Eq(t, task, out)
|
|
}
|
|
|
|
func TestTask_Constrain(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
|
|
// Add a constraint to the task
|
|
out := task.Constrain(NewConstraint("kernel.name", "=", "darwin"))
|
|
must.Len(t, 1, task.Constraints)
|
|
|
|
// Check that the task was returned
|
|
must.Eq(t, task, out)
|
|
|
|
// Add a second constraint
|
|
task.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000"))
|
|
expect := []*Constraint{
|
|
{
|
|
LTarget: "kernel.name",
|
|
RTarget: "darwin",
|
|
Operand: "=",
|
|
},
|
|
{
|
|
LTarget: "memory.totalbytes",
|
|
RTarget: "128000000",
|
|
Operand: ">=",
|
|
},
|
|
}
|
|
must.Eq(t, expect, task.Constraints)
|
|
}
|
|
|
|
func TestTask_AddAffinity(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
task := NewTask("task1", "exec")
|
|
|
|
// Add an affinity to the task
|
|
out := task.AddAffinity(NewAffinity("kernel.version", "=", "4.6", 100))
|
|
must.Len(t, 1, out.Affinities)
|
|
|
|
// Check that the task was returned
|
|
must.Eq(t, task, out)
|
|
|
|
// Add a second affinity
|
|
task.AddAffinity(NewAffinity("${node.datacenter}", "=", "dc2", 50))
|
|
expect := []*Affinity{
|
|
{
|
|
LTarget: "kernel.version",
|
|
RTarget: "4.6",
|
|
Operand: "=",
|
|
Weight: pointerOf(int8(100)),
|
|
},
|
|
{
|
|
LTarget: "${node.datacenter}",
|
|
RTarget: "dc2",
|
|
Operand: "=",
|
|
Weight: pointerOf(int8(50)),
|
|
},
|
|
}
|
|
must.Eq(t, expect, task.Affinities)
|
|
}
|
|
|
|
func TestTask_Artifact(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
a := TaskArtifact{
|
|
GetterSource: pointerOf("http://localhost/foo.txt"),
|
|
GetterMode: pointerOf("file"),
|
|
GetterHeaders: make(map[string]string),
|
|
GetterOptions: make(map[string]string),
|
|
}
|
|
a.Canonicalize()
|
|
must.Eq(t, "file", *a.GetterMode)
|
|
must.Eq(t, false, *a.GetterInsecure)
|
|
must.Eq(t, "local/foo.txt", filepath.ToSlash(*a.RelativeDest))
|
|
must.Nil(t, a.GetterOptions)
|
|
must.Nil(t, a.GetterHeaders)
|
|
must.Eq(t, false, a.Chown)
|
|
}
|
|
|
|
func TestTask_VolumeMount(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
vm := new(VolumeMount)
|
|
vm.Canonicalize()
|
|
must.NotNil(t, vm.PropagationMode)
|
|
must.Eq(t, "private", *vm.PropagationMode)
|
|
}
|
|
|
|
func TestTask_Canonicalize_TaskLifecycle(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
expected *TaskLifecycle
|
|
task *Task
|
|
}{
|
|
{
|
|
name: "empty",
|
|
task: &Task{
|
|
Lifecycle: &TaskLifecycle{},
|
|
},
|
|
expected: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
}
|
|
j := &Job{
|
|
ID: pointerOf("test"),
|
|
}
|
|
tc.task.Canonicalize(tg, j)
|
|
must.Eq(t, tc.expected, tc.task.Lifecycle)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTask_Template_WaitConfig_Canonicalize_and_Copy(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
taskWithWait := func(wc *WaitConfig) *Task {
|
|
return &Task{
|
|
Templates: []*Template{
|
|
{
|
|
Wait: wc,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
canonicalized *WaitConfig
|
|
copied *WaitConfig
|
|
task *Task
|
|
}{
|
|
{
|
|
name: "all-fields",
|
|
task: taskWithWait(&WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
Max: pointerOf(time.Duration(10)),
|
|
}),
|
|
canonicalized: &WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
Max: pointerOf(time.Duration(10)),
|
|
},
|
|
copied: &WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
Max: pointerOf(time.Duration(10)),
|
|
},
|
|
},
|
|
{
|
|
name: "no-fields",
|
|
task: taskWithWait(&WaitConfig{}),
|
|
canonicalized: &WaitConfig{
|
|
Min: nil,
|
|
Max: nil,
|
|
},
|
|
copied: &WaitConfig{
|
|
Min: nil,
|
|
Max: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "min-only",
|
|
task: taskWithWait(&WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
}),
|
|
canonicalized: &WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
},
|
|
copied: &WaitConfig{
|
|
Min: pointerOf(time.Duration(5)),
|
|
},
|
|
},
|
|
{
|
|
name: "max-only",
|
|
task: taskWithWait(&WaitConfig{
|
|
Max: pointerOf(time.Duration(10)),
|
|
}),
|
|
canonicalized: &WaitConfig{
|
|
Max: pointerOf(time.Duration(10)),
|
|
},
|
|
copied: &WaitConfig{
|
|
Max: pointerOf(time.Duration(10)),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
}
|
|
j := &Job{
|
|
ID: pointerOf("test"),
|
|
}
|
|
must.Eq(t, tc.copied, tc.task.Templates[0].Wait.Copy())
|
|
tc.task.Canonicalize(tg, j)
|
|
must.Eq(t, tc.canonicalized, tc.task.Templates[0].Wait)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTask_Canonicalize_Vault(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
input *Vault
|
|
expected *Vault
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: &Vault{},
|
|
expected: &Vault{
|
|
Env: pointerOf(true),
|
|
DisableFile: pointerOf(false),
|
|
Namespace: pointerOf(""),
|
|
Cluster: "default",
|
|
ChangeMode: pointerOf("restart"),
|
|
ChangeSignal: pointerOf("SIGHUP"),
|
|
AllowTokenExpiration: pointerOf(false),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.input.Canonicalize()
|
|
must.Eq(t, tc.expected, tc.input)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensures no regression on https://github.com/hashicorp/nomad/issues/3132
|
|
func TestTaskGroup_Canonicalize_Update(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
// Job with an Empty() Update
|
|
job := &Job{
|
|
ID: pointerOf("test"),
|
|
Update: &UpdateStrategy{
|
|
AutoRevert: pointerOf(false),
|
|
AutoPromote: pointerOf(false),
|
|
Canary: pointerOf(0),
|
|
HealthCheck: pointerOf(""),
|
|
HealthyDeadline: pointerOf(time.Duration(0)),
|
|
ProgressDeadline: pointerOf(time.Duration(0)),
|
|
MaxParallel: pointerOf(0),
|
|
MinHealthyTime: pointerOf(time.Duration(0)),
|
|
Stagger: pointerOf(time.Duration(0)),
|
|
},
|
|
}
|
|
job.Canonicalize()
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
}
|
|
tg.Canonicalize(job)
|
|
must.NotNil(t, job.Update)
|
|
must.Nil(t, tg.Update)
|
|
}
|
|
|
|
func TestTaskGroup_Canonicalize_Scaling(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
job := &Job{
|
|
ID: pointerOf("test"),
|
|
}
|
|
job.Canonicalize()
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
Count: nil,
|
|
Scaling: &ScalingPolicy{
|
|
Min: nil,
|
|
Max: pointerOf(int64(10)),
|
|
Policy: nil,
|
|
Enabled: nil,
|
|
CreateIndex: 0,
|
|
ModifyIndex: 0,
|
|
},
|
|
}
|
|
job.TaskGroups = []*TaskGroup{tg}
|
|
|
|
// both nil => both == 1
|
|
tg.Canonicalize(job)
|
|
must.Positive(t, *tg.Count)
|
|
must.NotNil(t, tg.Scaling.Min)
|
|
must.Eq(t, 1, *tg.Count)
|
|
must.Eq(t, int64(*tg.Count), *tg.Scaling.Min)
|
|
|
|
// count == nil => count = Scaling.Min
|
|
tg.Count = nil
|
|
tg.Scaling.Min = pointerOf(int64(5))
|
|
tg.Canonicalize(job)
|
|
must.Positive(t, *tg.Count)
|
|
must.NotNil(t, tg.Scaling.Min)
|
|
must.Eq(t, 5, *tg.Count)
|
|
must.Eq(t, int64(*tg.Count), *tg.Scaling.Min)
|
|
|
|
// Scaling.Min == nil => Scaling.Min == count
|
|
tg.Count = pointerOf(5)
|
|
tg.Scaling.Min = nil
|
|
tg.Canonicalize(job)
|
|
must.Positive(t, *tg.Count)
|
|
must.NotNil(t, tg.Scaling.Min)
|
|
must.Eq(t, 5, *tg.Scaling.Min)
|
|
must.Eq(t, int64(*tg.Count), *tg.Scaling.Min)
|
|
|
|
// both present, both persisted
|
|
tg.Count = pointerOf(5)
|
|
tg.Scaling.Min = pointerOf(int64(1))
|
|
tg.Canonicalize(job)
|
|
must.Positive(t, *tg.Count)
|
|
must.NotNil(t, tg.Scaling.Min)
|
|
must.Eq(t, 1, *tg.Scaling.Min)
|
|
must.Eq(t, 5, *tg.Count)
|
|
}
|
|
|
|
func TestTaskGroup_Merge_Update(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
job := &Job{
|
|
ID: pointerOf("test"),
|
|
Update: &UpdateStrategy{},
|
|
}
|
|
job.Canonicalize()
|
|
|
|
// Merge and canonicalize part of an update block
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
Update: &UpdateStrategy{
|
|
AutoRevert: pointerOf(true),
|
|
Canary: pointerOf(5),
|
|
HealthCheck: pointerOf("foo"),
|
|
},
|
|
}
|
|
|
|
tg.Canonicalize(job)
|
|
must.Eq(t, &UpdateStrategy{
|
|
AutoRevert: pointerOf(true),
|
|
AutoPromote: pointerOf(false),
|
|
Canary: pointerOf(5),
|
|
HealthCheck: pointerOf("foo"),
|
|
HealthyDeadline: pointerOf(5 * time.Minute),
|
|
ProgressDeadline: pointerOf(10 * time.Minute),
|
|
MaxParallel: pointerOf(1),
|
|
MinHealthyTime: pointerOf(10 * time.Second),
|
|
Stagger: pointerOf(30 * time.Second),
|
|
}, tg.Update)
|
|
}
|
|
|
|
// Verifies that migrate strategy is merged correctly
|
|
func TestTaskGroup_Canonicalize_MigrateStrategy(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
type testCase struct {
|
|
desc string
|
|
jobType string
|
|
jobMigrate *MigrateStrategy
|
|
taskMigrate *MigrateStrategy
|
|
expected *MigrateStrategy
|
|
}
|
|
|
|
testCases := []testCase{
|
|
{
|
|
desc: "Default batch",
|
|
jobType: "batch",
|
|
jobMigrate: nil,
|
|
taskMigrate: nil,
|
|
expected: nil,
|
|
},
|
|
{
|
|
desc: "Default service",
|
|
jobType: "service",
|
|
jobMigrate: nil,
|
|
taskMigrate: nil,
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(1),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(10 * time.Second),
|
|
HealthyDeadline: pointerOf(5 * time.Minute),
|
|
},
|
|
},
|
|
{
|
|
desc: "Empty job migrate strategy",
|
|
jobType: "service",
|
|
jobMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(0),
|
|
HealthCheck: pointerOf(""),
|
|
MinHealthyTime: pointerOf(time.Duration(0)),
|
|
HealthyDeadline: pointerOf(time.Duration(0)),
|
|
},
|
|
taskMigrate: nil,
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(0),
|
|
HealthCheck: pointerOf(""),
|
|
MinHealthyTime: pointerOf(time.Duration(0)),
|
|
HealthyDeadline: pointerOf(time.Duration(0)),
|
|
},
|
|
},
|
|
{
|
|
desc: "Inherit from job",
|
|
jobType: "service",
|
|
jobMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(3),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
taskMigrate: nil,
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(3),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
},
|
|
{
|
|
desc: "Set in task",
|
|
jobType: "service",
|
|
jobMigrate: nil,
|
|
taskMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(3),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(3),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
},
|
|
{
|
|
desc: "Merge from job",
|
|
jobType: "service",
|
|
jobMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(11),
|
|
},
|
|
taskMigrate: &MigrateStrategy{
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(11),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
},
|
|
{
|
|
desc: "Override from group",
|
|
jobType: "service",
|
|
jobMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(11),
|
|
},
|
|
taskMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(5),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(5),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(time.Duration(2)),
|
|
HealthyDeadline: pointerOf(time.Duration(2)),
|
|
},
|
|
},
|
|
{
|
|
desc: "Parallel from job, defaulting",
|
|
jobType: "service",
|
|
jobMigrate: &MigrateStrategy{
|
|
MaxParallel: pointerOf(5),
|
|
},
|
|
taskMigrate: nil,
|
|
expected: &MigrateStrategy{
|
|
MaxParallel: pointerOf(5),
|
|
HealthCheck: pointerOf("checks"),
|
|
MinHealthyTime: pointerOf(10 * time.Second),
|
|
HealthyDeadline: pointerOf(5 * time.Minute),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
job := &Job{
|
|
ID: pointerOf("test"),
|
|
Migrate: tc.jobMigrate,
|
|
Type: pointerOf(tc.jobType),
|
|
}
|
|
job.Canonicalize()
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
Migrate: tc.taskMigrate,
|
|
}
|
|
tg.Canonicalize(job)
|
|
must.Eq(t, tc.expected, tg.Migrate)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSpread_Canonicalize asserts that the spread block is canonicalized correctly
|
|
func TestSpread_Canonicalize(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
job := &Job{
|
|
ID: pointerOf("test"),
|
|
Type: pointerOf("batch"),
|
|
}
|
|
job.Canonicalize()
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("foo"),
|
|
}
|
|
type testCase struct {
|
|
desc string
|
|
spread *Spread
|
|
expectedWeight int8
|
|
}
|
|
cases := []testCase{
|
|
{
|
|
"Nil spread",
|
|
&Spread{
|
|
Attribute: "test",
|
|
Weight: nil,
|
|
},
|
|
50,
|
|
},
|
|
{
|
|
"Zero spread",
|
|
&Spread{
|
|
Attribute: "test",
|
|
Weight: pointerOf(int8(0)),
|
|
},
|
|
0,
|
|
},
|
|
{
|
|
"Non Zero spread",
|
|
&Spread{
|
|
Attribute: "test",
|
|
Weight: pointerOf(int8(100)),
|
|
},
|
|
100,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
tg.Spreads = []*Spread{tc.spread}
|
|
tg.Canonicalize(job)
|
|
for _, spr := range tg.Spreads {
|
|
must.Eq(t, tc.expectedWeight, *spr.Weight)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_NewDefaultReschedulePolicy(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
inputJobType string
|
|
expected *ReschedulePolicy
|
|
}{
|
|
{
|
|
desc: "service job type",
|
|
inputJobType: "service",
|
|
expected: &ReschedulePolicy{
|
|
Attempts: pointerOf(0),
|
|
Interval: pointerOf(time.Duration(0)),
|
|
Delay: pointerOf(30 * time.Second),
|
|
DelayFunction: pointerOf("exponential"),
|
|
MaxDelay: pointerOf(1 * time.Hour),
|
|
Unlimited: pointerOf(true),
|
|
},
|
|
},
|
|
{
|
|
desc: "batch job type",
|
|
inputJobType: "batch",
|
|
expected: &ReschedulePolicy{
|
|
Attempts: pointerOf(1),
|
|
Interval: pointerOf(24 * time.Hour),
|
|
Delay: pointerOf(5 * time.Second),
|
|
DelayFunction: pointerOf("constant"),
|
|
MaxDelay: pointerOf(time.Duration(0)),
|
|
Unlimited: pointerOf(false),
|
|
},
|
|
},
|
|
{
|
|
desc: "system job type",
|
|
inputJobType: "system",
|
|
expected: &ReschedulePolicy{
|
|
Attempts: pointerOf(0),
|
|
Interval: pointerOf(time.Duration(0)),
|
|
Delay: pointerOf(time.Duration(0)),
|
|
DelayFunction: pointerOf(""),
|
|
MaxDelay: pointerOf(time.Duration(0)),
|
|
Unlimited: pointerOf(false),
|
|
},
|
|
},
|
|
{
|
|
desc: "unrecognised job type",
|
|
inputJobType: "unrecognised",
|
|
expected: &ReschedulePolicy{
|
|
Attempts: pointerOf(0),
|
|
Interval: pointerOf(time.Duration(0)),
|
|
Delay: pointerOf(time.Duration(0)),
|
|
DelayFunction: pointerOf(""),
|
|
MaxDelay: pointerOf(time.Duration(0)),
|
|
Unlimited: pointerOf(false),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
actual := NewDefaultReschedulePolicy(tc.inputJobType)
|
|
must.Eq(t, tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskGroup_Canonicalize_Consul(t *testing.T) {
|
|
testutil.Parallel(t)
|
|
|
|
t.Run("override job consul in group", func(t *testing.T) {
|
|
job := &Job{
|
|
ID: pointerOf("job"),
|
|
ConsulNamespace: pointerOf("ns1"),
|
|
}
|
|
job.Canonicalize()
|
|
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("group"),
|
|
Consul: &Consul{Namespace: "ns2"},
|
|
}
|
|
tg.Canonicalize(job)
|
|
|
|
must.Eq(t, "ns1", *job.ConsulNamespace)
|
|
must.Eq(t, "ns2", tg.Consul.Namespace)
|
|
})
|
|
|
|
t.Run("inherit job consul in group", func(t *testing.T) {
|
|
job := &Job{
|
|
ID: pointerOf("job"),
|
|
ConsulNamespace: pointerOf("ns1"),
|
|
}
|
|
job.Canonicalize()
|
|
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("group"),
|
|
Consul: nil, // not set, inherit from job
|
|
}
|
|
tg.Canonicalize(job)
|
|
|
|
must.Eq(t, "ns1", *job.ConsulNamespace)
|
|
must.Eq(t, "ns1", tg.Consul.Namespace)
|
|
})
|
|
|
|
t.Run("set in group only", func(t *testing.T) {
|
|
job := &Job{
|
|
ID: pointerOf("job"),
|
|
ConsulNamespace: nil,
|
|
}
|
|
job.Canonicalize()
|
|
|
|
tg := &TaskGroup{
|
|
Name: pointerOf("group"),
|
|
Consul: &Consul{Namespace: "ns2"},
|
|
}
|
|
tg.Canonicalize(job)
|
|
|
|
must.Eq(t, "", *job.ConsulNamespace)
|
|
must.Eq(t, "ns2", tg.Consul.Namespace)
|
|
})
|
|
}
|