open-nomad/nomad/job_endpoint_hooks_test.go

809 lines
18 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package nomad
import (
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require"
)
func Test_jobImpliedConstraints_Mutate(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
inputJob *structs.Job
expectedOutputJob *structs.Job
expectedOutputWarnings []error
expectedOutputError error
name string
}{
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "no needed constraints",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{},
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Vault: &structs.Vault{},
Name: "group1-task1",
},
},
Constraints: []*structs.Constraint{vaultConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task with vault",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Vault: &structs.Vault{},
Name: "group1-task1",
},
{
Vault: &structs.Vault{},
Name: "group1-task2",
},
{
Vault: &structs.Vault{},
Name: "group1-task3",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Vault: &structs.Vault{},
Name: "group1-task1",
},
{
Vault: &structs.Vault{},
Name: "group1-task2",
},
{
Vault: &structs.Vault{},
Name: "group1-task3",
},
},
Constraints: []*structs.Constraint{vaultConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "group with multiple tasks with vault",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
},
},
},
{
Name: "group2",
Tasks: []*structs.Task{
{
Name: "group2-task1",
Vault: &structs.Vault{},
},
},
},
{
Name: "group3",
Tasks: []*structs.Task{
{
Name: "group3-task1",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
},
},
},
{
Name: "group2",
Tasks: []*structs.Task{
{
Name: "group2-task1",
Vault: &structs.Vault{},
},
},
Constraints: []*structs.Constraint{vaultConstraint},
},
{
Name: "group3",
Tasks: []*structs.Task{
{
Name: "group3-task1",
},
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "multiple groups only one with vault",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{},
},
},
Constraints: []*structs.Constraint{
{
LTarget: attrVaultVersion,
RTarget: ">= 1.0.0",
Operand: structs.ConstraintSemver,
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{},
},
},
Constraints: []*structs.Constraint{
{
LTarget: attrVaultVersion,
RTarget: ">= 1.0.0",
Operand: structs.ConstraintSemver,
},
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "existing vault version constraint",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{},
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "group1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{},
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
vaultConstraint,
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "vault with other constraints",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{
ChangeSignal: "SIGINT",
ChangeMode: structs.VaultChangeModeSignal,
},
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Vault: &structs.Vault{
ChangeSignal: "SIGINT",
ChangeMode: structs.VaultChangeModeSignal,
},
},
},
Constraints: []*structs.Constraint{
vaultConstraint,
{
LTarget: "${attr.os.signals}",
RTarget: "SIGINT",
Operand: "set_contains",
},
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task with vault signal change",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
KillSignal: "SIGINT",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
KillSignal: "SIGINT",
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${attr.os.signals}",
RTarget: "SIGINT",
Operand: "set_contains",
},
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task with kill signal",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Templates: []*structs.Template{
{
ChangeMode: "signal",
ChangeSignal: "SIGINT",
},
},
},
{
Name: "group1-task2",
Templates: []*structs.Template{
{
ChangeMode: "signal",
ChangeSignal: "SIGHUP",
},
},
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Tasks: []*structs.Task{
{
Name: "group1-task1",
Templates: []*structs.Template{
{
ChangeMode: "signal",
ChangeSignal: "SIGINT",
},
},
},
{
Name: "group1-task2",
Templates: []*structs.Template{
{
ChangeMode: "signal",
ChangeSignal: "SIGHUP",
},
},
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${attr.os.signals}",
RTarget: "SIGHUP,SIGINT",
Operand: "set_contains",
},
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "multiple tasks with template signal change",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
Constraints: []*structs.Constraint{nativeServiceDiscoveryConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group nomad discovery",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
Constraints: []*structs.Constraint{nativeServiceDiscoveryConstraint},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
Constraints: []*structs.Constraint{nativeServiceDiscoveryConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group nomad discovery constraint found",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderNomad,
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
nativeServiceDiscoveryConstraint,
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group nomad discovery other constraints",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
Constraints: []*structs.Constraint{consulServiceDiscoveryConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group Consul discovery",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
Constraints: []*structs.Constraint{consulServiceDiscoveryConstraint},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
Constraints: []*structs.Constraint{consulServiceDiscoveryConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group Consul discovery constraint found",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
Provider: structs.ServiceProviderConsul,
},
},
Constraints: []*structs.Constraint{
{
LTarget: "${node.class}",
RTarget: "high-memory",
Operand: "=",
},
consulServiceDiscoveryConstraint,
},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group Consul discovery other constraints",
},
{
inputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
},
},
},
},
},
expectedOutputJob: &structs.Job{
Name: "example",
TaskGroups: []*structs.TaskGroup{
{
Name: "example-group-1",
Services: []*structs.Service{
{
Name: "example-group-service-1",
},
},
Constraints: []*structs.Constraint{consulServiceDiscoveryConstraint},
},
},
},
expectedOutputWarnings: nil,
expectedOutputError: nil,
name: "task group with empty provider",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
impl := jobImpliedConstraints{}
actualJob, actualWarnings, actualError := impl.Mutate(tc.inputJob)
require.Equal(t, tc.expectedOutputJob, actualJob)
require.ElementsMatch(t, tc.expectedOutputWarnings, actualWarnings)
require.Equal(t, tc.expectedOutputError, actualError)
})
}
}
func Test_jobCanonicalizer_Mutate(t *testing.T) {
ci.Parallel(t)
serverJobDefaultPriority := 100
testCases := []struct {
name string
inputJob *structs.Job
expectedOutputJob *structs.Job
}{
{
name: "no mutation",
inputJob: &structs.Job{
Namespace: "default",
Datacenters: []string{"*"},
Priority: 123,
},
expectedOutputJob: &structs.Job{
Namespace: "default",
Datacenters: []string{"*"},
Priority: 123,
},
},
{
name: "when priority is 0 mutate using the value present in the server config",
inputJob: &structs.Job{
Namespace: "default",
Datacenters: []string{"*"},
Priority: 0,
},
expectedOutputJob: &structs.Job{
Namespace: "default",
Datacenters: []string{"*"},
Priority: serverJobDefaultPriority,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
impl := jobCanonicalizer{srv: &Server{config: &Config{JobDefaultPriority: serverJobDefaultPriority}}}
actualJob, actualWarnings, actualError := impl.Mutate(tc.inputJob)
must.Eq(t, tc.expectedOutputJob, actualJob)
must.NoError(t, actualError)
must.Nil(t, actualWarnings)
})
}
}
func TestJob_submissionController(t *testing.T) {
ci.Parallel(t)
args := &structs.JobRegisterRequest{
Submission: &structs.JobSubmission{
Source: "this is some hcl content",
Format: "hcl2",
Variables: "variables",
},
}
t.Run("nil", func(t *testing.T) {
j := &Job{srv: &Server{
config: &Config{JobMaxSourceSize: 1024},
}}
err := j.submissionController(&structs.JobRegisterRequest{
Submission: nil,
})
must.NoError(t, err)
})
t.Run("under max size", func(t *testing.T) {
j := &Job{srv: &Server{
config: &Config{JobMaxSourceSize: 1024},
}}
err := j.submissionController(args)
must.NoError(t, err)
must.NotNil(t, args.Submission)
})
t.Run("over max size", func(t *testing.T) {
j := &Job{srv: &Server{
config: &Config{JobMaxSourceSize: 1},
}}
err := j.submissionController(args)
must.ErrorContains(t, err, "job source size of 33 B exceeds maximum of 1 B and will be discarded")
must.Nil(t, args.Submission)
})
}