Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

service discovery: add config boolean parameter and fingerprinting #12201

Merged
merged 2 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/fingerprint/nomad.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fingerprint

import (
"strconv"

log "github.com/hashicorp/go-hclog"
)

Expand All @@ -20,6 +22,7 @@ func (f *NomadFingerprint) Fingerprint(req *FingerprintRequest, resp *Fingerprin
resp.AddAttribute("nomad.advertise.address", req.Node.HTTPAddr)
resp.AddAttribute("nomad.version", req.Config.Version.VersionNumber())
resp.AddAttribute("nomad.revision", req.Config.Version.Revision)
resp.AddAttribute("nomad.service_discovery", strconv.FormatBool(req.Config.NomadServiceDiscovery))
resp.Detected = true
return nil
}
5 changes: 5 additions & 0 deletions client/fingerprint/nomad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/version"
"github.com/stretchr/testify/require"
)

func TestNomadFingerprint(t *testing.T) {
Expand All @@ -20,6 +21,7 @@ func TestNomadFingerprint(t *testing.T) {
Revision: r,
Version: v,
},
NomadServiceDiscovery: true,
}
node := &structs.Node{
Attributes: make(map[string]string),
Expand Down Expand Up @@ -52,4 +54,7 @@ func TestNomadFingerprint(t *testing.T) {
if response.Attributes["nomad.advertise.address"] != h {
t.Fatalf("incorrect advertise address")
}

serviceDisco := response.Attributes["nomad.service_discovery"]
require.Equal(t, "true", serviceDisco, "service_discovery attr incorrect")
}
35 changes: 34 additions & 1 deletion nomad/job_endpoint_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ var (
RTarget: ">= 0.6.1",
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
// we need to ensure task groups are placed where they can run
// successfully.
nativeServiceDiscoveryConstraint = &structs.Constraint{
LTarget: "${attr.nomad.service_discovery}",
RTarget: "true",
Operand: "=",
}
)

type admissionController interface {
Expand Down Expand Up @@ -120,8 +131,11 @@ func (jobImpliedConstraints) Mutate(j *structs.Job) (*structs.Job, []error, erro
// Get the required signals
signals := j.RequiredSignals()

// Identify which task groups are utilising Nomad native service discovery.
nativeServiceDisco := j.RequiredNativeServiceDiscovery()

// Hot path
if len(signals) == 0 && len(policies) == 0 {
if len(signals) == 0 && len(policies) == 0 && len(nativeServiceDisco) == 0 {
return j, nil, nil
}

Expand Down Expand Up @@ -171,6 +185,25 @@ func (jobImpliedConstraints) Mutate(j *structs.Job) (*structs.Job, []error, erro
}
}

// 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)
}
}

return j, nil, nil
}

Expand Down
169 changes: 169 additions & 0 deletions nomad/job_endpoint_hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package nomad

import (
"testing"

"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)

func Test_jobImpliedConstraints_Mutate(t *testing.T) {
t.Parallel()

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: "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",
},
}

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)
})
}
}
38 changes: 38 additions & 0 deletions nomad/structs/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,41 @@ type JobServiceRegistrationsResponse struct {
Services []*ServiceRegistration
QueryMeta
}

// RequiredNativeServiceDiscovery identifies which task groups, if any, within
// the job are utilising Nomad native service discovery.
func (j *Job) RequiredNativeServiceDiscovery() map[string]bool {
groups := make(map[string]bool)

for _, tg := range j.TaskGroups {

// It is possible for services using the Nomad provider to be
// configured at the task group level, so check here first.
if requiresNativeServiceDiscovery(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 requiresNativeServiceDiscovery(task.Services) {
groups[tg.Name] = true
continue
}
}
}

return groups
}

// requiresNativeServiceDiscovery identifies whether any of the services passed
// to the function are utilising Nomad native service discovery.
func requiresNativeServiceDiscovery(services []*Service) bool {
for _, tgService := range services {
if tgService.Provider == ServiceProviderNomad {
return true
}
}
return false
}
Loading