job_hooks: add implicit constraint when using Consul for services. (#12602)
This commit is contained in:
parent
42068f8823
commit
010acce59f
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
consul: Added implicit Consul constraint for task groups utilising Consul service and check registrations
|
||||
```
|
|
@ -72,6 +72,8 @@ func TestPrevAlloc_StreamAllocDir_TLS(t *testing.T) {
|
|||
Operand: "=",
|
||||
},
|
||||
}
|
||||
job.TaskGroups[0].Constraints = nil
|
||||
job.TaskGroups[0].Tasks[0].Services = nil
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].EphemeralDisk.Sticky = true
|
||||
job.TaskGroups[0].EphemeralDisk.Migrate = true
|
||||
|
|
|
@ -441,6 +441,7 @@ func TestClient_UpdateAllocStatus(t *testing.T) {
|
|||
job := mock.Job()
|
||||
// allow running job on any node including self client, that may not be a Linux box
|
||||
job.Constraints = nil
|
||||
job.TaskGroups[0].Constraints = nil
|
||||
job.TaskGroups[0].Count = 1
|
||||
task := job.TaskGroups[0].Tasks[0]
|
||||
task.Driver = "mock_driver"
|
||||
|
|
|
@ -53,7 +53,7 @@ func TestIntegration_Command_RoundTripJob(t *testing.T) {
|
|||
defer srv.Shutdown()
|
||||
|
||||
{
|
||||
cmd := exec.Command("nomad", "job", "init")
|
||||
cmd := exec.Command("nomad", "job", "init", "-short")
|
||||
cmd.Dir = tmpDir
|
||||
assert.Nil(cmd.Run())
|
||||
}
|
||||
|
|
|
@ -24,6 +24,16 @@ var (
|
|||
Operand: structs.ConstraintSemver,
|
||||
}
|
||||
|
||||
// consulServiceDiscoveryConstraint is the implicit constraint added to
|
||||
// task groups which include services utilising the Consul provider. The
|
||||
// Consul version is pinned to a minimum of that which introduced the
|
||||
// namespace feature.
|
||||
consulServiceDiscoveryConstraint = &structs.Constraint{
|
||||
LTarget: "${attr.consul.version}",
|
||||
RTarget: ">= 1.7.0",
|
||||
Operand: structs.ConstraintSemver,
|
||||
}
|
||||
|
||||
// nativeServiceDiscoveryConstraint is the constraint injected into task
|
||||
// groups that utilise Nomad's native service discovery feature. This is
|
||||
// needed, as operators can disable the client functionality, and therefore
|
||||
|
@ -134,79 +144,96 @@ func (jobImpliedConstraints) Mutate(j *structs.Job) (*structs.Job, []error, erro
|
|||
// Identify which task groups are utilising Nomad native service discovery.
|
||||
nativeServiceDisco := j.RequiredNativeServiceDiscovery()
|
||||
|
||||
// Identify which task groups are utilising Consul service discovery.
|
||||
consulServiceDisco := j.RequiredConsulServiceDiscovery()
|
||||
|
||||
// Hot path
|
||||
if len(signals) == 0 && len(vaultBlocks) == 0 && len(nativeServiceDisco) == 0 {
|
||||
if len(signals) == 0 && len(vaultBlocks) == 0 &&
|
||||
len(nativeServiceDisco) == 0 && len(consulServiceDisco) == 0 {
|
||||
return j, nil, nil
|
||||
}
|
||||
|
||||
// Add Vault constraints if no Vault constraint exists
|
||||
// Iterate through all the task groups within the job and add any required
|
||||
// constraints. When adding new implicit constraints, they should go inside
|
||||
// this single loop, with a new constraintMatcher if needed.
|
||||
for _, tg := range j.TaskGroups {
|
||||
_, ok := vaultBlocks[tg.Name]
|
||||
if !ok {
|
||||
// Not requesting Vault
|
||||
continue
|
||||
|
||||
// If the task group utilises Vault, run the mutator.
|
||||
if _, ok := vaultBlocks[tg.Name]; ok {
|
||||
mutateConstraint(constraintMatcherLeft, tg, vaultConstraint)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range tg.Constraints {
|
||||
if c.LTarget == vaultConstraintLTarget {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
// Check whether the task group is using signals. In the case that it
|
||||
// is, we flatten the signals and build a constraint, then run the
|
||||
// mutator.
|
||||
if tgSignals, ok := signals[tg.Name]; ok {
|
||||
required := helper.MapStringStringSliceValueSet(tgSignals)
|
||||
sigConstraint := getSignalConstraint(required)
|
||||
mutateConstraint(constraintMatcherFull, tg, sigConstraint)
|
||||
}
|
||||
|
||||
if !found {
|
||||
tg.Constraints = append(tg.Constraints, vaultConstraint)
|
||||
}
|
||||
}
|
||||
|
||||
// Add signal constraints
|
||||
for _, tg := range j.TaskGroups {
|
||||
tgSignals, ok := signals[tg.Name]
|
||||
if !ok {
|
||||
// Not requesting signal
|
||||
continue
|
||||
// If the task group utilises Nomad service discovery, run the mutator.
|
||||
if ok := nativeServiceDisco[tg.Name]; ok {
|
||||
mutateConstraint(constraintMatcherFull, tg, nativeServiceDiscoveryConstraint)
|
||||
}
|
||||
|
||||
// Flatten the signals
|
||||
required := helper.MapStringStringSliceValueSet(tgSignals)
|
||||
sigConstraint := getSignalConstraint(required)
|
||||
|
||||
found := false
|
||||
for _, c := range tg.Constraints {
|
||||
if c.Equals(sigConstraint) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
tg.Constraints = append(tg.Constraints, sigConstraint)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the Nomad service discovery constraints.
|
||||
for _, tg := range j.TaskGroups {
|
||||
if ok := nativeServiceDisco[tg.Name]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range tg.Constraints {
|
||||
if c.Equals(nativeServiceDiscoveryConstraint) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
tg.Constraints = append(tg.Constraints, nativeServiceDiscoveryConstraint)
|
||||
// If the task group utilises Consul service discovery, run the mutator.
|
||||
if ok := consulServiceDisco[tg.Name]; ok {
|
||||
mutateConstraint(constraintMatcherLeft, tg, consulServiceDiscoveryConstraint)
|
||||
}
|
||||
}
|
||||
|
||||
return j, nil, nil
|
||||
}
|
||||
|
||||
// constraintMatcher is a custom type which helps control how constraints are
|
||||
// identified as being present within a task group.
|
||||
type constraintMatcher uint
|
||||
|
||||
const (
|
||||
// constraintMatcherFull ensures that a constraint is only considered found
|
||||
// when they match totally. This check is performed using the
|
||||
// structs.Constraint Equals function.
|
||||
constraintMatcherFull constraintMatcher = iota
|
||||
|
||||
// constraintMatcherLeft ensure that a constraint is considered found if
|
||||
// the constraints LTarget is matched only. This allows an existing
|
||||
// constraint to override the proposed implicit one.
|
||||
constraintMatcherLeft
|
||||
)
|
||||
|
||||
// mutateConstraint is a generic mutator used to set implicit constraints
|
||||
// within the task group if they are needed.
|
||||
func mutateConstraint(matcher constraintMatcher, taskGroup *structs.TaskGroup, constraint *structs.Constraint) {
|
||||
|
||||
var found bool
|
||||
|
||||
// It's possible to switch on the matcher within the constraint loop to
|
||||
// reduce repetition. This, however, means switching per constraint,
|
||||
// therefore we do it here.
|
||||
switch matcher {
|
||||
case constraintMatcherFull:
|
||||
for _, c := range taskGroup.Constraints {
|
||||
if c.Equals(constraint) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
case constraintMatcherLeft:
|
||||
for _, c := range taskGroup.Constraints {
|
||||
if c.LTarget == constraint.LTarget {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a suitable constraint match, add one.
|
||||
if !found {
|
||||
taskGroup.Constraints = append(taskGroup.Constraints, constraint)
|
||||
}
|
||||
}
|
||||
|
||||
// jobValidate validates a Job and task drivers and returns an error if there is
|
||||
// a validation problem or if the Job is of a type a user is not allowed to
|
||||
// submit.
|
||||
|
|
|
@ -39,6 +39,406 @@ func Test_jobImpliedConstraints_Mutate(t *testing.T) {
|
|||
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: vaultConstraintLTarget,
|
||||
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: vaultConstraintLTarget,
|
||||
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",
|
||||
|
@ -156,6 +556,155 @@ func Test_jobImpliedConstraints_Mutate(t *testing.T) {
|
|||
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 {
|
||||
|
|
|
@ -1646,15 +1646,13 @@ func TestJobEndpoint_Register_Vault_Policies(t *testing.T) {
|
|||
t.Fatalf("vault token not cleared")
|
||||
}
|
||||
|
||||
// Check that an implicit constraint was created
|
||||
// Check that an implicit constraints were created for Vault and Consul.
|
||||
constraints := out.TaskGroups[0].Constraints
|
||||
if l := len(constraints); l != 1 {
|
||||
if l := len(constraints); l != 2 {
|
||||
t.Fatalf("Unexpected number of tests: %v", l)
|
||||
}
|
||||
|
||||
if !constraints[0].Equal(vaultConstraint) {
|
||||
t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint)
|
||||
}
|
||||
require.ElementsMatch(t, constraints, []*structs.Constraint{consulServiceDiscoveryConstraint, vaultConstraint})
|
||||
|
||||
// Create the register request with another job asking for a vault policy but
|
||||
// send the root Vault token
|
||||
|
@ -6482,15 +6480,11 @@ func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) {
|
|||
t.Fatalf("index mis-match")
|
||||
}
|
||||
|
||||
// Check that there is an implicit vault constraint
|
||||
constraints := out.TaskGroups[0].Constraints
|
||||
if len(constraints) != 1 {
|
||||
t.Fatalf("Expected an implicit constraint")
|
||||
}
|
||||
|
||||
if !constraints[0].Equal(vaultConstraint) {
|
||||
t.Fatalf("Expected implicit vault constraint")
|
||||
}
|
||||
// Check that there is an implicit Vault and Consul constraint.
|
||||
require.Len(t, out.TaskGroups[0].Constraints, 2)
|
||||
require.ElementsMatch(t, out.TaskGroups[0].Constraints, []*structs.Constraint{
|
||||
consulServiceDiscoveryConstraint, vaultConstraint,
|
||||
})
|
||||
}
|
||||
|
||||
func TestJobEndpoint_ValidateJob_ConsulConnect(t *testing.T) {
|
||||
|
@ -6640,20 +6634,11 @@ func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) {
|
|||
t.Fatalf("index mis-match")
|
||||
}
|
||||
|
||||
// Check that there is an implicit signal constraint
|
||||
constraints := out.TaskGroups[0].Constraints
|
||||
if len(constraints) != 1 {
|
||||
t.Fatalf("Expected an implicit constraint")
|
||||
}
|
||||
|
||||
sigConstraint := getSignalConstraint([]string{signal1, signal2})
|
||||
if !strings.HasPrefix(sigConstraint.RTarget, "SIGHUP") {
|
||||
t.Fatalf("signals not sorted: %v", sigConstraint.RTarget)
|
||||
}
|
||||
|
||||
if !constraints[0].Equal(sigConstraint) {
|
||||
t.Fatalf("Expected implicit vault constraint")
|
||||
}
|
||||
// Check that there is an implicit signal and Consul constraint.
|
||||
require.Len(t, out.TaskGroups[0].Constraints, 2)
|
||||
require.ElementsMatch(t, out.TaskGroups[0].Constraints, []*structs.Constraint{
|
||||
getSignalConstraint([]string{signal1, signal2}), consulServiceDiscoveryConstraint},
|
||||
)
|
||||
}
|
||||
|
||||
func TestJobEndpoint_ValidateJobUpdate(t *testing.T) {
|
||||
|
|
|
@ -34,6 +34,7 @@ func Node() *structs.Node {
|
|||
"nomad.version": "0.5.0",
|
||||
"driver.exec": "1",
|
||||
"driver.mock_driver": "1",
|
||||
"consul.version": "1.11.4",
|
||||
},
|
||||
|
||||
// TODO Remove once clientv2 gets merged
|
||||
|
@ -251,6 +252,13 @@ func Job() *structs.Job {
|
|||
{
|
||||
Name: "web",
|
||||
Count: 10,
|
||||
Constraints: []*structs.Constraint{
|
||||
{
|
||||
LTarget: "${attr.consul.version}",
|
||||
RTarget: ">= 1.7.0",
|
||||
Operand: structs.ConstraintSemver,
|
||||
},
|
||||
},
|
||||
EphemeralDisk: &structs.EphemeralDisk{
|
||||
SizeMB: 150,
|
||||
},
|
||||
|
|
|
@ -60,3 +60,42 @@ func requiresNativeServiceDiscovery(services []*Service) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RequiredConsulServiceDiscovery identifies which task groups, if any, within
|
||||
// the job are utilising Consul service discovery.
|
||||
func (j *Job) RequiredConsulServiceDiscovery() map[string]bool {
|
||||
groups := make(map[string]bool)
|
||||
|
||||
for _, tg := range j.TaskGroups {
|
||||
|
||||
// It is possible for services using the Consul provider to be
|
||||
// configured at the task group level, so check here first. This is
|
||||
// a requirement for Consul Connect services.
|
||||
if requiresConsulServiceDiscovery(tg.Services) {
|
||||
groups[tg.Name] = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate the tasks within the task group to check the services
|
||||
// configured at this more traditional level.
|
||||
for _, task := range tg.Tasks {
|
||||
if requiresConsulServiceDiscovery(task.Services) {
|
||||
groups[tg.Name] = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
// requiresConsulServiceDiscovery identifies whether any of the services passed
|
||||
// to the function are utilising Consul service discovery.
|
||||
func requiresConsulServiceDiscovery(services []*Service) bool {
|
||||
for _, tgService := range services {
|
||||
if tgService.Provider == ServiceProviderConsul || tgService.Provider == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -154,3 +154,147 @@ func TestJob_RequiresNativeServiceDiscovery(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJob_RequiredConsulServiceDiscovery(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inputJob *Job
|
||||
expectedOutput map[string]bool
|
||||
name string
|
||||
}{
|
||||
{
|
||||
inputJob: &Job{
|
||||
TaskGroups: []*TaskGroup{
|
||||
{
|
||||
Name: "group1",
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "group2",
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: map[string]bool{"group1": true, "group2": true},
|
||||
name: "multiple group services with Consul provider",
|
||||
},
|
||||
{
|
||||
inputJob: &Job{
|
||||
TaskGroups: []*TaskGroup{
|
||||
{
|
||||
Name: "group1",
|
||||
Tasks: []*Task{
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "group2",
|
||||
Tasks: []*Task{
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "consul"},
|
||||
{Provider: "consul"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: map[string]bool{"group1": true, "group2": true},
|
||||
name: "multiple task services with Consul provider",
|
||||
},
|
||||
{
|
||||
inputJob: &Job{
|
||||
TaskGroups: []*TaskGroup{
|
||||
{
|
||||
Name: "group1",
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "group2",
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: map[string]bool{},
|
||||
name: "multiple group services with Nomad provider",
|
||||
},
|
||||
{
|
||||
inputJob: &Job{
|
||||
TaskGroups: []*TaskGroup{
|
||||
{
|
||||
Name: "group1",
|
||||
Tasks: []*Task{
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "group2",
|
||||
Tasks: []*Task{
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Services: []*Service{
|
||||
{Provider: "nomad"},
|
||||
{Provider: "nomad"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: map[string]bool{},
|
||||
name: "multiple task services with Nomad provider",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actualOutput := tc.inputJob.RequiredConsulServiceDiscovery()
|
||||
require.Equal(t, tc.expectedOutput, actualOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue