parse affinities and constraints on devices

This commit is contained in:
Alex Dadgar 2018-10-11 14:05:19 -07:00
parent 939c3fd85f
commit 5a07f9f96e
7 changed files with 150 additions and 54 deletions

View file

@ -121,6 +121,14 @@ type RequestedDevice struct {
// Count is the number of requested devices // Count is the number of requested devices
Count *uint64 Count *uint64
// Constraints are a set of constraints to apply when selecting the device
// to use.
Constraints []*Constraint
// Affinities are a set of affinites to apply when selecting the device
// to use.
Affinities []*Affinity
} }
func (d *RequestedDevice) Canonicalize() { func (d *RequestedDevice) Canonicalize() {

View file

@ -605,22 +605,8 @@ func ApiJobToStructJob(job *api.Job) *structs.Job {
Payload: job.Payload, Payload: job.Payload,
Meta: job.Meta, Meta: job.Meta,
VaultToken: *job.VaultToken, VaultToken: *job.VaultToken,
} Constraints: ApiConstraintsToStructs(job.Constraints),
Affinities: ApiAffinitiesToStructs(job.Affinities),
if l := len(job.Constraints); l != 0 {
j.Constraints = make([]*structs.Constraint, l)
for i, c := range job.Constraints {
con := &structs.Constraint{}
ApiConstraintToStructs(c, con)
j.Constraints[i] = con
}
}
if l := len(job.Affinities); l != 0 {
j.Affinities = make([]*structs.Affinity, l)
for i, a := range job.Affinities {
j.Affinities[i] = ApiAffinityToStructs(a)
}
} }
// COMPAT: Remove in 0.7.0. Update has been pushed into the task groups // COMPAT: Remove in 0.7.0. Update has been pushed into the task groups
@ -679,22 +665,8 @@ func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
tg.Name = *taskGroup.Name tg.Name = *taskGroup.Name
tg.Count = *taskGroup.Count tg.Count = *taskGroup.Count
tg.Meta = taskGroup.Meta tg.Meta = taskGroup.Meta
tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints)
if l := len(taskGroup.Constraints); l != 0 { tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities)
tg.Constraints = make([]*structs.Constraint, l)
for k, constraint := range taskGroup.Constraints {
c := &structs.Constraint{}
ApiConstraintToStructs(constraint, c)
tg.Constraints[k] = c
}
}
if l := len(taskGroup.Affinities); l != 0 {
tg.Affinities = make([]*structs.Affinity, l)
for k, affinity := range taskGroup.Affinities {
tg.Affinities[k] = ApiAffinityToStructs(affinity)
}
}
tg.RestartPolicy = &structs.RestartPolicy{ tg.RestartPolicy = &structs.RestartPolicy{
Attempts: *taskGroup.RestartPolicy.Attempts, Attempts: *taskGroup.RestartPolicy.Attempts,
@ -772,22 +744,8 @@ func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
structsTask.KillTimeout = *apiTask.KillTimeout structsTask.KillTimeout = *apiTask.KillTimeout
structsTask.ShutdownDelay = apiTask.ShutdownDelay structsTask.ShutdownDelay = apiTask.ShutdownDelay
structsTask.KillSignal = apiTask.KillSignal structsTask.KillSignal = apiTask.KillSignal
structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints)
if l := len(apiTask.Constraints); l != 0 { structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities)
structsTask.Constraints = make([]*structs.Constraint, l)
for i, constraint := range apiTask.Constraints {
c := &structs.Constraint{}
ApiConstraintToStructs(constraint, c)
structsTask.Constraints[i] = c
}
}
if l := len(apiTask.Affinities); l != 0 {
structsTask.Affinities = make([]*structs.Affinity, l)
for i, a := range apiTask.Affinities {
structsTask.Affinities[i] = ApiAffinityToStructs(a)
}
}
if l := len(apiTask.Services); l != 0 { if l := len(apiTask.Services); l != 0 {
structsTask.Services = make([]*structs.Service, l) structsTask.Services = make([]*structs.Service, l)
@ -935,6 +893,8 @@ func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
out.Devices[i] = &structs.RequestedDevice{ out.Devices[i] = &structs.RequestedDevice{
Name: d.Name, Name: d.Name,
Count: *d.Count, Count: *d.Count,
Constraints: ApiConstraintsToStructs(d.Constraints),
Affinities: ApiAffinitiesToStructs(d.Affinities),
} }
} }
} }
@ -942,10 +902,42 @@ func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
return out return out
} }
func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) { func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
c2.LTarget = c1.LTarget if in == nil {
c2.RTarget = c1.RTarget return nil
c2.Operand = c1.Operand }
out := make([]*structs.Constraint, len(in))
for i, ac := range in {
out[i] = ApiConstraintToStructs(ac)
}
return out
}
func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint {
if in == nil {
return nil
}
return &structs.Constraint{
LTarget: in.LTarget,
RTarget: in.RTarget,
Operand: in.Operand,
}
}
func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity {
if in == nil {
return nil
}
out := make([]*structs.Affinity, len(in))
for i, ac := range in {
out[i] = ApiAffinityToStructs(ac)
}
return out
} }
func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {

View file

@ -1420,6 +1420,21 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
{ {
Name: "nvidia/gpu", Name: "nvidia/gpu",
Count: helper.Uint64ToPtr(4), Count: helper.Uint64ToPtr(4),
Constraints: []*api.Constraint{
{
LTarget: "x",
RTarget: "y",
Operand: "z",
},
},
Affinities: []*api.Affinity{
{
LTarget: "a",
RTarget: "b",
Operand: "c",
Weight: 50,
},
},
}, },
{ {
Name: "gpu", Name: "gpu",
@ -1704,6 +1719,21 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
{ {
Name: "nvidia/gpu", Name: "nvidia/gpu",
Count: 4, Count: 4,
Constraints: []*structs.Constraint{
{
LTarget: "x",
RTarget: "y",
Operand: "z",
},
},
Affinities: []*structs.Affinity{
{
LTarget: "a",
RTarget: "b",
Operand: "c",
Weight: 50,
},
},
}, },
{ {
Name: "gpu", Name: "gpu",

View file

@ -1480,10 +1480,20 @@ func parseResources(result *api.Resources, list *ast.ObjectList) error {
} }
name := do.Keys[0].Token.Value().(string) name := do.Keys[0].Token.Value().(string)
// Value should be an object
var listVal *ast.ObjectList
if ot, ok := do.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("device should be an object")
}
// Check for invalid keys // Check for invalid keys
valid := []string{ valid := []string{
"name", "name",
"count", "count",
"affinity",
"constraint",
} }
if err := helper.CheckHCLKeys(do.Val, valid); err != nil { if err := helper.CheckHCLKeys(do.Val, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx)) return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx))
@ -1497,10 +1507,28 @@ func parseResources(result *api.Resources, list *ast.ObjectList) error {
if err := hcl.DecodeObject(&m, do.Val); err != nil { if err := hcl.DecodeObject(&m, do.Val); err != nil {
return err return err
} }
delete(m, "constraint")
delete(m, "affinity")
if err := mapstructure.WeakDecode(m, &r); err != nil { if err := mapstructure.WeakDecode(m, &r); err != nil {
return err return err
} }
// Parse constraints
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
if err := parseConstraints(&r.Constraints, o); err != nil {
return multierror.Prefix(err, "constraint ->")
}
}
// Parse affinities
if o := listVal.Filter("affinity"); len(o.Items) > 0 {
if err := parseAffinities(&r.Affinities, o); err != nil {
return multierror.Prefix(err, "affinity ->")
}
}
result.Devices[idx] = &r result.Devices[idx] = &r
} }
} }

View file

@ -234,6 +234,21 @@ func TestParse(t *testing.T) {
{ {
Name: "nvidia/gpu", Name: "nvidia/gpu",
Count: helper.Uint64ToPtr(10), Count: helper.Uint64ToPtr(10),
Constraints: []*api.Constraint{
{
LTarget: "${driver.attr.memory}",
RTarget: "2GB",
Operand: ">",
},
},
Affinities: []*api.Affinity{
{
LTarget: "${driver.model}",
RTarget: "1080ti",
Operand: "=",
Weight: 50,
},
},
}, },
{ {
Name: "intel/gpu", Name: "intel/gpu",

View file

@ -199,6 +199,17 @@ job "binstore-storagelocker" {
device "nvidia/gpu" { device "nvidia/gpu" {
count = 10 count = 10
constraint {
attribute = "${driver.attr.memory}"
value = "2GB"
operator = ">"
}
affinity {
attribute = "${driver.model}"
value = "1080ti"
weight = 50
}
} }
device "intel/gpu" {} device "intel/gpu" {}

View file

@ -2032,6 +2032,15 @@ type RequestedDevice struct {
// Count is the number of requested devices // Count is the number of requested devices
Count uint64 Count uint64
// TODO validate
// Constraints are a set of constraints to apply when selecting the device
// to use.
Constraints []*Constraint
// Affinities are a set of affinites to apply when selecting the device
// to use.
Affinities []*Affinity
} }
func (r *RequestedDevice) Copy() *RequestedDevice { func (r *RequestedDevice) Copy() *RequestedDevice {
@ -2040,6 +2049,9 @@ func (r *RequestedDevice) Copy() *RequestedDevice {
} }
nr := *r nr := *r
nr.Constraints = CopySliceConstraints(nr.Constraints)
nr.Affinities = CopySliceAffinities(nr.Affinities)
return &nr return &nr
} }