From c98d1d54eed51b5b80456e9250909de7e484e0a0 Mon Sep 17 00:00:00 2001 From: vsoch Date: Sun, 5 May 2024 18:39:22 -0600 Subject: [PATCH 1/8] feat: add function to return named slots Signed-off-by: vsoch --- examples/nextgen/v1/example1/example.go | 22 ++++++++- pkg/nextgen/v1/jobspec.go | 59 +++++++++++++++++++------ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/examples/nextgen/v1/example1/example.go b/examples/nextgen/v1/example1/example.go index ffc8bfb..c235394 100644 --- a/examples/nextgen/v1/example1/example.go +++ b/examples/nextgen/v1/example1/example.go @@ -51,15 +51,33 @@ func main() { fmt.Println(string(out)) // Test getting slots - slots := js.GetSlots() + slots, err := js.GetSlots() + if err != nil { + fmt.Printf("error getting slots:%s\n", err) + os.Exit(1) + } fmt.Printf("Found %d slots\n", len(slots)) for _, slot := range slots { fmt.Println(slot) } - slots = js.GetScheduledSlots() + slots, err = js.GetScheduledSlots() + if err != nil { + fmt.Printf("error getting scheduled slots:%s\n", err) + os.Exit(1) + } fmt.Printf("Found %d scheduled slots\n", len(slots)) for _, slot := range slots { fmt.Println(slot) } + + named, err := js.GetScheduledNamedSlots() + if err != nil { + fmt.Printf("error getting scheduled named slots:%s\n", err) + os.Exit(1) + } + fmt.Printf("Found %d scheduled named slots\n", len(slots)) + for name, _ := range named { + fmt.Println(name) + } } diff --git a/pkg/nextgen/v1/jobspec.go b/pkg/nextgen/v1/jobspec.go index 6a1b3ed..289a19d 100644 --- a/pkg/nextgen/v1/jobspec.go +++ b/pkg/nextgen/v1/jobspec.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "fmt" "os" "reflect" @@ -35,7 +36,7 @@ func (js *Jobspec) JobspecToYaml() (string, error) { } // GetSlots returns all slots in resources -func (js *Jobspec) GetSlots() []Resource { +func (js *Jobspec) GetSlots() ([]Resource, error) { emptyResource := Resource{} slots := []Resource{} @@ -50,10 +51,18 @@ func (js *Jobspec) GetSlots() []Resource { // Slot at the top level already! if resource.Type == "slot" { + + // If the slot doesn't have a label, no go + if resource.Label == "" { + return slots, fmt.Errorf("slots are required to have a label") + } slots = append(slots, resource) } for _, with := range resource.With { - slot := getSlots(with) + slot, err := getSlots(with) + if err != nil { + return slots, err + } if !reflect.DeepEqual(emptyResource, slot) { slots = append(slots, slot) } @@ -62,16 +71,19 @@ func (js *Jobspec) GetSlots() []Resource { // If we found no slots, assume all top level resources are slots if len(slots) == 0 { - return fauxSlots + return fauxSlots, nil } - return slots + return slots, nil } // GetScheduledSlots returns all slots marked for scheduling // If none are marked, we assume they all are -func (js *Jobspec) GetScheduledSlots() []Resource { +func (js *Jobspec) GetScheduledSlots() ([]Resource, error) { - slots := js.GetSlots() + slots, err := js.GetSlots() + if err != nil { + return slots, err + } scheduled := []Resource{} allTrue := true @@ -83,9 +95,24 @@ func (js *Jobspec) GetScheduledSlots() []Resource { } // If none marked for scheduling, they all should be if allTrue { - return slots + return slots, nil } - return scheduled + return scheduled, nil +} + +// GetScheduledNamedSlots returns slots as a lookup by name +func (js *Jobspec) GetScheduledNamedSlots() (map[string]Resource, error) { + + slots, err := js.GetScheduledSlots() + named := map[string]Resource{} + if err != nil { + return named, err + } + + for _, slot := range slots { + named[slot.Label] = slot + } + return named, nil } // A fauxSlot will only be use if we don't have any actual slots @@ -100,23 +127,29 @@ func generateFauxSlot(name string, resource Resource) Resource { } // getSlots is a recursive helper function that takes resources explicitly -func getSlots(resource Resource) Resource { +func getSlots(resource Resource) (Resource, error) { emptyResource := Resource{} // If we find a slot, stop here if resource.Type == "slot" { - return resource + if resource.Label == "" { + return resource, fmt.Errorf("slots are required to have a label") + } + return resource, nil } for _, with := range resource.With { - slot := getSlots(with) + slot, err := getSlots(with) + if err != nil { + return slot, err + } // If we find a slot if !reflect.DeepEqual(emptyResource, slot) { - return slot + return slot, nil } } - return emptyResource + return emptyResource, nil } // JobspectoJson convets back to json string From b4da819f77bab570ae1a27b9206a2c39a9b55817 Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 7 May 2024 13:32:55 -0600 Subject: [PATCH 2/8] wip: named slots Signed-off-by: vsoch --- examples/nextgen/v1/example1/example.go | 20 ++---- pkg/nextgen/v1/jobspec.go | 54 +++++--------- pkg/nextgen/v1/schema.json | 60 ++++++---------- pkg/nextgen/v1/types.go | 94 +++++++++++++++++++------ 4 files changed, 112 insertions(+), 116 deletions(-) diff --git a/examples/nextgen/v1/example1/example.go b/examples/nextgen/v1/example1/example.go index c235394..4a84c3e 100644 --- a/examples/nextgen/v1/example1/example.go +++ b/examples/nextgen/v1/example1/example.go @@ -51,33 +51,21 @@ func main() { fmt.Println(string(out)) // Test getting slots - slots, err := js.GetSlots() - if err != nil { - fmt.Printf("error getting slots:%s\n", err) - os.Exit(1) - } + slots := js.GetSlots() fmt.Printf("Found %d slots\n", len(slots)) for _, slot := range slots { fmt.Println(slot) } - slots, err = js.GetScheduledSlots() - if err != nil { - fmt.Printf("error getting scheduled slots:%s\n", err) - os.Exit(1) - } + slots = js.GetScheduledSlots() fmt.Printf("Found %d scheduled slots\n", len(slots)) for _, slot := range slots { fmt.Println(slot) } - named, err := js.GetScheduledNamedSlots() - if err != nil { - fmt.Printf("error getting scheduled named slots:%s\n", err) - os.Exit(1) - } + named := js.GetScheduledNamedSlots() fmt.Printf("Found %d scheduled named slots\n", len(slots)) - for name, _ := range named { + for name := range named { fmt.Println(name) } } diff --git a/pkg/nextgen/v1/jobspec.go b/pkg/nextgen/v1/jobspec.go index 289a19d..d9d940a 100644 --- a/pkg/nextgen/v1/jobspec.go +++ b/pkg/nextgen/v1/jobspec.go @@ -2,7 +2,6 @@ package v1 import ( "encoding/json" - "fmt" "os" "reflect" @@ -36,7 +35,7 @@ func (js *Jobspec) JobspecToYaml() (string, error) { } // GetSlots returns all slots in resources -func (js *Jobspec) GetSlots() ([]Resource, error) { +func (js *Jobspec) GetSlots() []Resource { emptyResource := Resource{} slots := []Resource{} @@ -51,18 +50,10 @@ func (js *Jobspec) GetSlots() ([]Resource, error) { // Slot at the top level already! if resource.Type == "slot" { - - // If the slot doesn't have a label, no go - if resource.Label == "" { - return slots, fmt.Errorf("slots are required to have a label") - } slots = append(slots, resource) } for _, with := range resource.With { - slot, err := getSlots(with) - if err != nil { - return slots, err - } + slot := getSlots(with) if !reflect.DeepEqual(emptyResource, slot) { slots = append(slots, slot) } @@ -71,19 +62,16 @@ func (js *Jobspec) GetSlots() ([]Resource, error) { // If we found no slots, assume all top level resources are slots if len(slots) == 0 { - return fauxSlots, nil + return fauxSlots } - return slots, nil + return slots } // GetScheduledSlots returns all slots marked for scheduling // If none are marked, we assume they all are -func (js *Jobspec) GetScheduledSlots() ([]Resource, error) { +func (js *Jobspec) GetScheduledSlots() []Resource { - slots, err := js.GetSlots() - if err != nil { - return slots, err - } + slots := js.GetSlots() scheduled := []Resource{} allTrue := true @@ -95,24 +83,20 @@ func (js *Jobspec) GetScheduledSlots() ([]Resource, error) { } // If none marked for scheduling, they all should be if allTrue { - return slots, nil + return slots } - return scheduled, nil + return scheduled } // GetScheduledNamedSlots returns slots as a lookup by name -func (js *Jobspec) GetScheduledNamedSlots() (map[string]Resource, error) { +func (js *Jobspec) GetScheduledNamedSlots() map[string]Resource { - slots, err := js.GetScheduledSlots() + slots := js.GetScheduledSlots() named := map[string]Resource{} - if err != nil { - return named, err - } - for _, slot := range slots { named[slot.Label] = slot } - return named, nil + return named } // A fauxSlot will only be use if we don't have any actual slots @@ -127,29 +111,23 @@ func generateFauxSlot(name string, resource Resource) Resource { } // getSlots is a recursive helper function that takes resources explicitly -func getSlots(resource Resource) (Resource, error) { +func getSlots(resource Resource) Resource { emptyResource := Resource{} // If we find a slot, stop here if resource.Type == "slot" { - if resource.Label == "" { - return resource, fmt.Errorf("slots are required to have a label") - } - return resource, nil + return resource } for _, with := range resource.With { - slot, err := getSlots(with) - if err != nil { - return slot, err - } + slot := getSlots(with) // If we find a slot if !reflect.DeepEqual(emptyResource, slot) { - return slot, nil + return slot } } - return emptyResource, nil + return emptyResource } // JobspectoJson convets back to json string diff --git a/pkg/nextgen/v1/schema.json b/pkg/nextgen/v1/schema.json index 97e49e1..0203290 100644 --- a/pkg/nextgen/v1/schema.json +++ b/pkg/nextgen/v1/schema.json @@ -12,14 +12,12 @@ "type": "integer", "enum": [1] }, - "requires": {"$ref": "#/definitions/requires"}, "resources": { "type": "object", "patternProperties": { "^([a-z]|[|]|&|[0-9]+)+$": {"$ref": "#/definitions/resources"} } }, - "attributes": {"$ref": "#/definitions/attributes"}, "groups": {"type": "array", "items": {"$ref": "#/definitions/group"}}, "tasks": {"$ref": "#/definitions/tasks"}, "additionalProperties": false @@ -34,31 +32,28 @@ "environment": {"type": "object"} } }, - "requires": { - "description": "compatibility requirements", - "type": "object" - }, "resources": { "description": "requested resources", "type": "object", - "required": ["type", "count"], + "required": ["type"], "properties": { - "type": {"type": "string"}, - "schedule": {"type": "boolean"}, - "exclusive": {"type": "boolean"}, + "type": {"enum": ["node"]}, "count": {"type": "integer", "minimum": 1}, - "unit": {"type": "string"}, - "with": {"$ref": "#/definitions/with"} - }, - "additionalProperties": false - }, - "with": { - "type": "array", - "minItems": 1, - "items": {"$ref": "#/definitions/resources"} + "requires": { + "type": "object", + "items": {"type": "object"} + }, + "attributes": {"$ref": "#/definitions/attributes"}, + "schedule": {"type": "boolean"}, + "with": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/definitions/resources"} + } + } }, "steps": { - "type": "array", + "type": ["array"], "items": { "type": "object", "properties": { @@ -76,15 +71,9 @@ "items": { "type": "object", "properties": { - "requires": {"$ref": "#/definitions/requires"}, - "resources": { - "oneOf": [ - {"$ref": "#/definitions/resources"}, - {"type": "string"} - ] - }, - "attributes": {"$ref": "#/definitions/attributes"}, + "resources": {"type": "string"}, "group": {"type": "string"}, + "local": {"type": "boolean"}, "name": {"type": "string"}, "depends_on": {"type": "array", "items": {"type": "string"}}, "replicas": {"type": "number", "minimum": 1, "default": 1}, @@ -102,18 +91,11 @@ "type": "object", "properties": { "name": {"type": "string"}, - "requires": {"$ref": "#/definitions/requires"}, - "resources": { - "oneOf": [ - {"$ref": "#/definitions/resources"}, - {"type": "string"} - ] - }, - "attributes": {"$ref": "#/definitions/attributes"}, + "resources": {"type": "string"}, "depends_on": {"type": "array", "items": {"type": "string"}}, - "tasks": {"$ref": "#/definitions/tasks"} + "tasks": {"$ref": "#/definitions/tasks"}, + "groups": {"type": "array", "items": {"$ref": "#/definitions/group"}} }, - "additionalProperties": false - } + "additionalProperties": false } } } diff --git a/pkg/nextgen/v1/types.go b/pkg/nextgen/v1/types.go index 5b09e09..c71a370 100644 --- a/pkg/nextgen/v1/types.go +++ b/pkg/nextgen/v1/types.go @@ -4,44 +4,48 @@ var ( jobspecVersion = 1 ) +// The JobSpec is what the user writes to describe their work type Jobspec struct { - Version int `json:"version" yaml:"version"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` - Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` - Requires map[string]string `json:"requires,omitempty" yaml:"requires,omitempty"` + Version int `json:"version" yaml:"version"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` + Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` + Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` + Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` } type Environment map[string]string type Resources map[string]Resource +type Requires map[string]map[string]string type Tasks []Task type Groups []Group type Task struct { - Group string `json:"group,omitempty" yaml:"group,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Replicas int `json:"replicas,omitempty" yaml:"replicas,omitempty"` - Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` - Command []string `json:"command,omitempty" yaml:"command,omitempty"` - Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` + Group string `json:"group,omitempty" yaml:"group,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Replicas int `json:"replicas,omitempty" yaml:"replicas,omitempty"` + Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` + Command []string `json:"command,omitempty" yaml:"command,omitempty"` + Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` } type Group struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` - Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` + Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` + Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` } type Resource struct { - Type string `yaml:"type,omitempty" json:"type,omitempty"` - Unit string `yaml:"unit,omitempty" json:"unit,omitempty"` - Count int32 `yaml:"count,omitempty" json:"count,omitempty"` - With []Resource `yaml:"with,omitempty" json:"with,omitempty"` - Label string `yaml:"label,omitempty" json:"label,omitempty"` - Exclusive bool `yaml:"exclusive,omitempty" json:"exclusive,omitempty"` - Schedule bool `yaml:"schedule,omitempty" json:"schedule,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + Unit string `yaml:"unit,omitempty" json:"unit,omitempty"` + Count int32 `yaml:"count,omitempty" json:"count,omitempty"` + With []Resource `yaml:"with,omitempty" json:"with,omitempty"` + Label string `yaml:"label,omitempty" json:"label,omitempty"` + Exclusive bool `yaml:"exclusive,omitempty" json:"exclusive,omitempty"` + Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` + Schedule bool `yaml:"schedule,omitempty" json:"schedule,omitempty"` } type Attributes struct { @@ -49,3 +53,47 @@ type Attributes struct { Cwd string `yaml:"cwd,omitempty" json:"cwd,omitempty"` Environment Environment `yaml:"environment,omitempty" json:"environment,omitempty"` } + +// Temporary holder type for named resource and paired requires +/*type ScheduledSlot struct { + Resource Resource + Requires Requires +} + +// A workload transforms a jobspec into workload units, each a schedulable unit +// This means that this is namespaced by the slots that we have, which are parsed +// here with GetScheduledNamedSlots +type Workload struct { + + // A slot can be for a task or group - it does not matter + Slots map[string]Slot `yaml:"slots,omitempty" json:"slots,omitempty"` +} + +// Slot is a unit of work with a top level resource request, +// a top level requirements specification (for an individual group or task +// at the top level) +type Slot struct { + Resources Resource `yaml:"resource,omitempty" json:"resource,omitempty"` + Requires SlotRequires `yaml:"requires,omitempty" json:"requires,omitempty"` +} + +// WorkloadRequires contins task / group level requirements for easy parsing +type SlotRequires struct { + + // Global describes the requirements for the entire task or group + Global Requires `yaml:"global,omitempty" json:"global,omitempty"` + + // Lookup of tasks and group requirements, if needed + Tasks map[string]Requires `yaml:"tasks,omitempty" json:"tasks,omitempty"` + Groups map[string]Requires `yaml:"groups,omitempty" json:"groups,omitempty"` +} + +// The WorkSpec describes the jobspec in schedulable units +/*type Workspec struct { + Version int `json:"version" yaml:"version"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` + Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` + Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` + Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` +}*/ From cadb71ea9522d8de24cab65cb6b9d55738a93dc1 Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 7 May 2024 16:19:28 -0600 Subject: [PATCH 3/8] test requires as an array Signed-off-by: vsoch --- pkg/nextgen/v1/schema.json | 4 ++-- pkg/nextgen/v1/types.go | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/nextgen/v1/schema.json b/pkg/nextgen/v1/schema.json index 0203290..86fda8d 100644 --- a/pkg/nextgen/v1/schema.json +++ b/pkg/nextgen/v1/schema.json @@ -37,10 +37,10 @@ "type": "object", "required": ["type"], "properties": { - "type": {"enum": ["node"]}, + "type": {"type": "string"}, "count": {"type": "integer", "minimum": 1}, "requires": { - "type": "object", + "type": "array", "items": {"type": "object"} }, "attributes": {"$ref": "#/definitions/attributes"}, diff --git a/pkg/nextgen/v1/types.go b/pkg/nextgen/v1/types.go index c71a370..82d64f1 100644 --- a/pkg/nextgen/v1/types.go +++ b/pkg/nextgen/v1/types.go @@ -6,27 +6,27 @@ var ( // The JobSpec is what the user writes to describe their work type Jobspec struct { - Version int `json:"version" yaml:"version"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` - Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` - Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Version int `json:"version" yaml:"version"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` + Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` + Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` + Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` } type Environment map[string]string type Resources map[string]Resource -type Requires map[string]map[string]string +type Requires map[string]string type Tasks []Task type Groups []Group type Task struct { - Group string `json:"group,omitempty" yaml:"group,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Replicas int `json:"replicas,omitempty" yaml:"replicas,omitempty"` - Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` - Command []string `json:"command,omitempty" yaml:"command,omitempty"` - Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Group string `json:"group,omitempty" yaml:"group,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Replicas int `json:"replicas,omitempty" yaml:"replicas,omitempty"` + Resources string `json:"resources,omitempty" yaml:"resources,omitempty"` + Command []string `json:"command,omitempty" yaml:"command,omitempty"` + Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` } type Group struct { From 4e43ba6db9a552d5946cdd376ce0b2f48048e868 Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 7 May 2024 18:11:56 -0600 Subject: [PATCH 4/8] remove extra code Signed-off-by: vsoch --- pkg/nextgen/v1/types.go | 46 +---------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/pkg/nextgen/v1/types.go b/pkg/nextgen/v1/types.go index 82d64f1..5d6392e 100644 --- a/pkg/nextgen/v1/types.go +++ b/pkg/nextgen/v1/types.go @@ -43,7 +43,7 @@ type Resource struct { With []Resource `yaml:"with,omitempty" json:"with,omitempty"` Label string `yaml:"label,omitempty" json:"label,omitempty"` Exclusive bool `yaml:"exclusive,omitempty" json:"exclusive,omitempty"` - Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` Schedule bool `yaml:"schedule,omitempty" json:"schedule,omitempty"` } @@ -53,47 +53,3 @@ type Attributes struct { Cwd string `yaml:"cwd,omitempty" json:"cwd,omitempty"` Environment Environment `yaml:"environment,omitempty" json:"environment,omitempty"` } - -// Temporary holder type for named resource and paired requires -/*type ScheduledSlot struct { - Resource Resource - Requires Requires -} - -// A workload transforms a jobspec into workload units, each a schedulable unit -// This means that this is namespaced by the slots that we have, which are parsed -// here with GetScheduledNamedSlots -type Workload struct { - - // A slot can be for a task or group - it does not matter - Slots map[string]Slot `yaml:"slots,omitempty" json:"slots,omitempty"` -} - -// Slot is a unit of work with a top level resource request, -// a top level requirements specification (for an individual group or task -// at the top level) -type Slot struct { - Resources Resource `yaml:"resource,omitempty" json:"resource,omitempty"` - Requires SlotRequires `yaml:"requires,omitempty" json:"requires,omitempty"` -} - -// WorkloadRequires contins task / group level requirements for easy parsing -type SlotRequires struct { - - // Global describes the requirements for the entire task or group - Global Requires `yaml:"global,omitempty" json:"global,omitempty"` - - // Lookup of tasks and group requirements, if needed - Tasks map[string]Requires `yaml:"tasks,omitempty" json:"tasks,omitempty"` - Groups map[string]Requires `yaml:"groups,omitempty" json:"groups,omitempty"` -} - -// The WorkSpec describes the jobspec in schedulable units -/*type Workspec struct { - Version int `json:"version" yaml:"version"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` - Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` - Requires map[string]Requires `json:"requires,omitempty" yaml:"requires,omitempty"` -}*/ From 6b9f9963a6462a70307809c91ea0215b1bcb021f Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 7 May 2024 19:18:04 -0600 Subject: [PATCH 5/8] ensure slot is represented with replicate Signed-off-by: vsoch --- examples/nextgen/v1/example1/example.go | 2 +- pkg/nextgen/v1/jobspec.go | 14 ++++++-------- pkg/nextgen/v1/types.go | 17 +++++++++-------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/examples/nextgen/v1/example1/example.go b/examples/nextgen/v1/example1/example.go index 4a84c3e..5d562ea 100644 --- a/examples/nextgen/v1/example1/example.go +++ b/examples/nextgen/v1/example1/example.go @@ -64,7 +64,7 @@ func main() { } named := js.GetScheduledNamedSlots() - fmt.Printf("Found %d scheduled named slots\n", len(slots)) + fmt.Printf("Found %d scheduled named slots\n", len(named)) for name := range named { fmt.Println(name) } diff --git a/pkg/nextgen/v1/jobspec.go b/pkg/nextgen/v1/jobspec.go index d9d940a..b94ebfa 100644 --- a/pkg/nextgen/v1/jobspec.go +++ b/pkg/nextgen/v1/jobspec.go @@ -48,8 +48,8 @@ func (js *Jobspec) GetSlots() []Resource { // a slot at the top level. We wrap in a faux slot fauxSlots = append(fauxSlots, generateFauxSlot(name, resource)) - // Slot at the top level already! - if resource.Type == "slot" { + // If replicas > 0, we have a slot at the level already + if resource.Replicas > 0 { slots = append(slots, resource) } for _, with := range resource.With { @@ -101,13 +101,11 @@ func (js *Jobspec) GetScheduledNamedSlots() map[string]Resource { // A fauxSlot will only be use if we don't have any actual slots func generateFauxSlot(name string, resource Resource) Resource { - return Resource{ - Type: "slot", - Label: name, - Count: 1, - Schedule: resource.Schedule, - With: []Resource{resource}, + resource.Replicas = 1 + if resource.Label == "" { + resource.Label = name } + return resource } // getSlots is a recursive helper function that takes resources explicitly diff --git a/pkg/nextgen/v1/types.go b/pkg/nextgen/v1/types.go index 5d6392e..8cda4f2 100644 --- a/pkg/nextgen/v1/types.go +++ b/pkg/nextgen/v1/types.go @@ -37,15 +37,16 @@ type Group struct { } type Resource struct { - Type string `yaml:"type,omitempty" json:"type,omitempty"` - Unit string `yaml:"unit,omitempty" json:"unit,omitempty"` - Count int32 `yaml:"count,omitempty" json:"count,omitempty"` - With []Resource `yaml:"with,omitempty" json:"with,omitempty"` - Label string `yaml:"label,omitempty" json:"label,omitempty"` - Exclusive bool `yaml:"exclusive,omitempty" json:"exclusive,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + Unit string `yaml:"unit,omitempty" json:"unit,omitempty"` + Replicas int32 `yaml:"replicas,omitempty" json:"replicas,omitempty"` + Count int32 `yaml:"count,omitempty" json:"count,omitempty"` + With []Resource `yaml:"with,omitempty" json:"with,omitempty"` + Label string `yaml:"label,omitempty" json:"label,omitempty"` + Exclusive bool `yaml:"exclusive,omitempty" json:"exclusive,omitempty"` Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` - Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` - Schedule bool `yaml:"schedule,omitempty" json:"schedule,omitempty"` + Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` + Schedule bool `yaml:"schedule,omitempty" json:"schedule,omitempty"` } type Attributes struct { From cfb3114778051f944d3050585287b33bd3e3c5de Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 7 May 2024 19:46:17 -0600 Subject: [PATCH 6/8] update generation to use replicas Signed-off-by: vsoch --- pkg/nextgen/v1/convert.go | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/pkg/nextgen/v1/convert.go b/pkg/nextgen/v1/convert.go index 6e98fe0..05726ad 100644 --- a/pkg/nextgen/v1/convert.go +++ b/pkg/nextgen/v1/convert.go @@ -23,15 +23,10 @@ func NewSimpleJobspec(name, command string, nodes, tasks int32) (*Jobspec, error // The node resource is what we are asking for nodeResource := Resource{ - Type: "node", - Count: nodes, - } - - // The slot is where we are doing an assessment for scheduling - slot := Resource{ - Type: "slot", - Count: int32(1), - Label: name, + Type: "node", + Count: nodes, + Replicas: 1, + Label: name, } // If tasks are defined, this is total tasks across the nodes @@ -41,12 +36,9 @@ func NewSimpleJobspec(name, command string, nodes, tasks int32) (*Jobspec, error Type: "core", Count: tasks, } - slot.With = []Resource{taskResource} + nodeResource.With = []Resource{taskResource} } - // And then the entire resource spec is added to the top level node resource - nodeResource.With = []Resource{slot} - // Resource name matches resources to named set resourceName := "task-resources" From 4f0d6ea55020403389644da919129ad939b9952c Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 9 May 2024 07:42:05 -0600 Subject: [PATCH 7/8] ensure generated jobspec has slot at rack Signed-off-by: vsoch --- pkg/nextgen/v1/convert.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/nextgen/v1/convert.go b/pkg/nextgen/v1/convert.go index 05726ad..f5f17e3 100644 --- a/pkg/nextgen/v1/convert.go +++ b/pkg/nextgen/v1/convert.go @@ -23,8 +23,13 @@ func NewSimpleJobspec(name, command string, nodes, tasks int32) (*Jobspec, error // The node resource is what we are asking for nodeResource := Resource{ - Type: "node", - Count: nodes, + Type: "node", + Count: nodes, + } + + // But we put it under the slot of a rack + rackResource := Resource{ + Type: "rack", Replicas: 1, Label: name, } @@ -40,6 +45,7 @@ func NewSimpleJobspec(name, command string, nodes, tasks int32) (*Jobspec, error } // Resource name matches resources to named set + rackResource.With = []Resource{nodeResource} resourceName := "task-resources" // Tasks reference the slot and command @@ -55,6 +61,6 @@ func NewSimpleJobspec(name, command string, nodes, tasks int32) (*Jobspec, error return &Jobspec{ Version: jobspecVersion, Tasks: tasklist, - Resources: Resources{resourceName: nodeResource}, + Resources: Resources{resourceName: rackResource}, }, nil } From a0e233cde040485478c9027f23c9103831ed406c Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 9 May 2024 23:34:19 -0600 Subject: [PATCH 8/8] allow attributes on the top level of jobspec These are for cluster state that is desired. We likely will want to move these into schedulable units. Signed-off-by: vsoch --- pkg/nextgen/v1/schema.json | 1 + pkg/nextgen/v1/types.go | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/nextgen/v1/schema.json b/pkg/nextgen/v1/schema.json index 86fda8d..a1d913d 100644 --- a/pkg/nextgen/v1/schema.json +++ b/pkg/nextgen/v1/schema.json @@ -20,6 +20,7 @@ }, "groups": {"type": "array", "items": {"$ref": "#/definitions/group"}}, "tasks": {"$ref": "#/definitions/tasks"}, + "attributes": {"$ref": "#/definitions/attributes"}, "additionalProperties": false }, "definitions": { diff --git a/pkg/nextgen/v1/types.go b/pkg/nextgen/v1/types.go index 8cda4f2..a44be37 100644 --- a/pkg/nextgen/v1/types.go +++ b/pkg/nextgen/v1/types.go @@ -6,12 +6,13 @@ var ( // The JobSpec is what the user writes to describe their work type Jobspec struct { - Version int `json:"version" yaml:"version"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` - Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` - Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Version int `json:"version" yaml:"version"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Resources Resources `json:"resources,omitempty" yaml:"resources,omitempty"` + Tasks Tasks `json:"tasks,omitempty" yaml:"tasks,omitempty"` + Groups Groups `json:"groups,omitempty" yaml:"groups,omitempty"` + Requires []Requires `json:"requires,omitempty" yaml:"requires,omitempty"` + Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty"` } type Environment map[string]string