parse affinities and constraints on devices
This commit is contained in:
parent
939c3fd85f
commit
5a07f9f96e
|
@ -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() {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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" {}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue