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

servicedisco: ensure service uniqueness in job validation #13869

Merged
merged 2 commits into from
Jul 21, 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 .changelog/13869.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
servicedisco: Fixed a bug where non-unique services would escape job validation
```
84 changes: 66 additions & 18 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ import (
"time"

jwt "github.com/golang-jwt/jwt/v4"
"github.com/hashicorp/nomad/helper/escapingfs"
"golang.org/x/crypto/blake2b"

"github.com/hashicorp/cronexpr"
"github.com/hashicorp/go-msgpack/codec"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-set"
"github.com/hashicorp/go-version"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/command/agent/host"
"github.com/hashicorp/nomad/command/agent/pprof"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/args"
"github.com/hashicorp/nomad/helper/constraints/semver"
"github.com/hashicorp/nomad/helper/escapingfs"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/lib/cpuset"
"github.com/hashicorp/nomad/lib/kheap"
psstructs "github.com/hashicorp/nomad/plugins/shared/structs"
"github.com/miekg/dns"
"github.com/mitchellh/copystructure"
"golang.org/x/crypto/blake2b"
)

var (
Expand Down Expand Up @@ -6522,6 +6522,7 @@ func (tg *TaskGroup) Validate(j *Job) error {
mErr.Errors = append(mErr.Errors, outer)
}
}

return mErr.ErrorOrNil()
}

Expand Down Expand Up @@ -6623,41 +6624,69 @@ func (tg *TaskGroup) validateNetworks() error {
// group service checks that refer to tasks only refer to tasks that exist.
func (tg *TaskGroup) validateServices() error {
var mErr multierror.Error
knownTasks := make(map[string]struct{})

// Track the providers used for this task group. Currently, Nomad only
// Accumulate task names in this group
taskSet := set.New[string](len(tg.Tasks))

// each service in a group must be unique (i.e. used in MakeAllocServiceID)
type unique struct {
name string
task string
port string
}

// Accumulate service IDs in this group
idSet := set.New[unique](0)

// Accumulate IDs that are duplicates
idDuplicateSet := set.New[unique](0)

// Accumulate the providers used for this task group. Currently, Nomad only
// allows the use of a single service provider within a task group.
configuredProviders := make(map[string]struct{})
providerSet := set.New[string](1)

// Create a map of known tasks and their services so we can compare
// vs the group-level services and checks
for _, task := range tg.Tasks {
knownTasks[task.Name] = struct{}{}
if task.Services == nil {
taskSet.Insert(task.Name)

if len(task.Services) == 0 {
continue
}

for _, service := range task.Services {

// Ensure no task-level checks specify a task
for _, check := range service.Checks {
if check.TaskName != "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s is invalid: only task group service checks can be assigned tasks", check.Name))
}
}

// Add the service provider to the tracking, if it has not already
// been seen.
if _, ok := configuredProviders[service.Provider]; !ok {
configuredProviders[service.Provider] = struct{}{}
// Track that we have seen this service id
id := unique{service.Name, task.Name, service.PortLabel}
if !idSet.Insert(id) {
// accumulate duplicates for a single error later on
idDuplicateSet.Insert(id)
}

// Track that we have seen this service provider
providerSet.Insert(service.Provider)
}
}

for i, service := range tg.Services {

// Add the service provider to the tracking, if it has not already been
// seen.
if _, ok := configuredProviders[service.Provider]; !ok {
configuredProviders[service.Provider] = struct{}{}
// Track that we have seen this service id
id := unique{service.Name, "group", service.PortLabel}
if !idSet.Insert(id) {
// accumulate duplicates for a single error later on
idDuplicateSet.Insert(id)
}

// Track that we have seen this service provider
providerSet.Insert(service.Provider)

if err := service.Validate(); err != nil {
outer := fmt.Errorf("Service[%d] %s validation failed: %s", i, service.Name, err)
mErr.Errors = append(mErr.Errors, outer)
Expand All @@ -6679,18 +6708,37 @@ func (tg *TaskGroup) validateServices() error {
if check.AddressMode == AddressModeDriver {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %q invalid: cannot use address_mode=\"driver\", only checks defined in a \"task\" service block can use this mode", service.Name))
}
if _, ok := knownTasks[check.TaskName]; !ok {
if !taskSet.Contains(check.TaskName) {
mErr.Errors = append(mErr.Errors,
fmt.Errorf("Check %s invalid: refers to non-existent task %s", check.Name, check.TaskName))
}
}
}
}

// Produce an error of any services which are not unique enough in the group
// i.e. have same <task, name, port>
if idDuplicateSet.Size() > 0 {
mErr.Errors = append(mErr.Errors,
fmt.Errorf(
"Services are not unique: %s",
idDuplicateSet.String(
func(u unique) string {
s := u.task + "->" + u.name
if u.port != "" {
s += ":" + u.port
}
return s
},
),
),
)
}

// The initial feature release of native service discovery only allows for
// a single service provider to be used across all services in a task
// group.
if len(configuredProviders) > 1 {
if providerSet.Size() > 1 {
mErr.Errors = append(mErr.Errors,
errors.New("Multiple service providers used: task group services must use the same provider"))
}
Expand Down