2019-08-15 15:22:37 +00:00
package nomad
import (
"fmt"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
)
2019-11-12 18:42:31 +00:00
const (
// vaultConstraintLTarget is the lefthand side of the Vault constraint
// injected when Vault policies are used. If an existing constraint
// with this target exists it overrides the injected constraint.
vaultConstraintLTarget = "${attr.vault.version}"
)
var (
// vaultConstraint is the implicit constraint added to jobs requesting a
// Vault token
vaultConstraint = & structs . Constraint {
LTarget : vaultConstraintLTarget ,
RTarget : ">= 0.6.1" ,
2019-11-13 23:36:15 +00:00
Operand : structs . ConstraintSemver ,
2019-11-12 18:42:31 +00:00
}
2022-03-14 11:42:12 +00:00
// 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 : "=" ,
}
2019-11-12 18:42:31 +00:00
)
2019-08-15 15:22:37 +00:00
type admissionController interface {
Name ( ) string
}
type jobMutator interface {
admissionController
Mutate ( * structs . Job ) ( out * structs . Job , warnings [ ] error , err error )
}
type jobValidator interface {
admissionController
Validate ( * structs . Job ) ( warnings [ ] error , err error )
}
func ( j * Job ) admissionControllers ( job * structs . Job ) ( out * structs . Job , warnings [ ] error , err error ) {
2021-03-10 14:12:46 +00:00
// Mutators run first before validators, so validators view the final rendered job.
// So, mutators must handle invalid jobs.
2019-08-15 15:22:37 +00:00
out , warnings , err = j . admissionMutators ( job )
if err != nil {
return nil , nil , err
}
validateWarnings , err := j . admissionValidators ( job )
if err != nil {
return nil , nil , err
}
warnings = append ( warnings , validateWarnings ... )
return out , warnings , nil
}
// admissionMutator returns an updated job as well as warnings or an error.
func ( j * Job ) admissionMutators ( job * structs . Job ) ( _ * structs . Job , warnings [ ] error , err error ) {
var w [ ] error
for _ , mutator := range j . mutators {
job , w , err = mutator . Mutate ( job )
j . logger . Trace ( "job mutate results" , "mutator" , mutator . Name ( ) , "warnings" , w , "error" , err )
if err != nil {
return nil , nil , fmt . Errorf ( "error in job mutator %s: %v" , mutator . Name ( ) , err )
}
warnings = append ( warnings , w ... )
}
return job , warnings , err
}
// admissionValidators returns a slice of validation warnings and a multierror
// of validation failures.
2019-10-28 14:49:08 +00:00
func ( j * Job ) admissionValidators ( origJob * structs . Job ) ( [ ] error , error ) {
2019-08-15 15:22:37 +00:00
// ensure job is not mutated
job := origJob . Copy ( )
2019-10-28 14:49:08 +00:00
var warnings [ ] error
var errs error
2019-08-15 15:22:37 +00:00
for _ , validator := range j . validators {
2019-10-28 14:49:08 +00:00
w , err := validator . Validate ( job )
2019-08-15 15:22:37 +00:00
j . logger . Trace ( "job validate results" , "validator" , validator . Name ( ) , "warnings" , w , "error" , err )
if err != nil {
2019-10-28 14:49:08 +00:00
errs = multierror . Append ( errs , err )
2019-08-15 15:22:37 +00:00
}
warnings = append ( warnings , w ... )
}
2019-10-28 14:49:08 +00:00
return warnings , errs
2019-08-15 15:22:37 +00:00
}
// jobCanonicalizer calls job.Canonicalize (sets defaults and initializes
// fields) and returns any errors as warnings.
type jobCanonicalizer struct { }
func ( jobCanonicalizer ) Name ( ) string {
return "canonicalize"
}
func ( jobCanonicalizer ) Mutate ( job * structs . Job ) ( * structs . Job , [ ] error , error ) {
2021-01-14 20:46:35 +00:00
job . Canonicalize ( )
return job , nil , nil
2019-08-15 15:22:37 +00:00
}
// jobImpliedConstraints adds constraints to a job implied by other job fields
// and stanzas.
type jobImpliedConstraints struct { }
func ( jobImpliedConstraints ) Name ( ) string {
return "constraints"
}
func ( jobImpliedConstraints ) Mutate ( j * structs . Job ) ( * structs . Job , [ ] error , error ) {
2022-04-05 18:18:10 +00:00
// Get the Vault blocks in the job
vaultBlocks := j . Vault ( )
2019-08-15 15:22:37 +00:00
// Get the required signals
signals := j . RequiredSignals ( )
2022-03-14 11:42:12 +00:00
// Identify which task groups are utilising Nomad native service discovery.
nativeServiceDisco := j . RequiredNativeServiceDiscovery ( )
2019-08-15 15:22:37 +00:00
// Hot path
2022-04-05 18:18:10 +00:00
if len ( signals ) == 0 && len ( vaultBlocks ) == 0 && len ( nativeServiceDisco ) == 0 {
2019-08-15 15:22:37 +00:00
return j , nil , nil
}
2019-11-12 18:42:31 +00:00
// Add Vault constraints if no Vault constraint exists
2019-08-15 15:22:37 +00:00
for _ , tg := range j . TaskGroups {
2022-04-05 18:18:10 +00:00
_ , ok := vaultBlocks [ tg . Name ]
2019-08-15 15:22:37 +00:00
if ! ok {
// Not requesting Vault
continue
}
found := false
for _ , c := range tg . Constraints {
2019-11-12 18:42:31 +00:00
if c . LTarget == vaultConstraintLTarget {
2019-08-15 15:22:37 +00:00
found = true
break
}
}
if ! found {
tg . Constraints = append ( tg . Constraints , vaultConstraint )
}
}
// Add signal constraints
for _ , tg := range j . TaskGroups {
tgSignals , ok := signals [ tg . Name ]
if ! ok {
2022-04-05 18:18:10 +00:00
// Not requesting signal
2019-08-15 15:22:37 +00:00
continue
}
// 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 )
}
}
2022-03-14 11:42:12 +00:00
// 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 )
}
}
2019-08-15 15:22:37 +00:00
return j , nil , nil
}
// 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.
type jobValidate struct { }
func ( jobValidate ) Name ( ) string {
return "validate"
}
func ( jobValidate ) Validate ( job * structs . Job ) ( warnings [ ] error , err error ) {
validationErrors := new ( multierror . Error )
if err := job . Validate ( ) ; err != nil {
multierror . Append ( validationErrors , err )
}
// Get any warnings
jobWarnings := job . Warnings ( )
if jobWarnings != nil {
if multi , ok := jobWarnings . ( * multierror . Error ) ; ok {
// Unpack multiple warnings
warnings = append ( warnings , multi . Errors ... )
} else {
warnings = append ( warnings , jobWarnings )
}
}
// TODO: Validate the driver configurations. These had to be removed in 0.9
// to support driver plugins, but see issue: #XXXX for more info.
if job . Type == structs . JobTypeCore {
multierror . Append ( validationErrors , fmt . Errorf ( "job type cannot be core" ) )
}
if len ( job . Payload ) != 0 {
multierror . Append ( validationErrors , fmt . Errorf ( "job can't be submitted with a payload, only dispatched" ) )
}
return warnings , validationErrors . ErrorOrNil ( )
}
2021-04-30 02:09:56 +00:00
type memoryOversubscriptionValidate struct {
srv * Server
}
func ( * memoryOversubscriptionValidate ) Name ( ) string {
return "memory_oversubscription"
}
func ( v * memoryOversubscriptionValidate ) Validate ( job * structs . Job ) ( warnings [ ] error , err error ) {
_ , c , err := v . srv . State ( ) . SchedulerConfig ( )
if err != nil {
return nil , err
}
if c != nil && c . MemoryOversubscriptionEnabled {
return nil , nil
}
for _ , tg := range job . TaskGroups {
for _ , t := range tg . Tasks {
if t . Resources != nil && t . Resources . MemoryMaxMB != 0 {
warnings = append ( warnings , fmt . Errorf ( "Memory oversubscription is not enabled; Task \"%v.%v\" memory_max value will be ignored. Update the Scheduler Configuration to allow oversubscription." , tg . Name , t . Name ) )
}
}
}
return warnings , err
}