Refactor + Multi admiralty test
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package models
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
|
||||
type ServiceResource struct {
|
||||
Action string `yaml:"action,omitempty"`
|
||||
SuccessCondition string `yaml:"successCondition,omitempty"`
|
||||
@@ -15,6 +17,24 @@ type Service struct {
|
||||
Spec ServiceSpec `yaml:"spec"`
|
||||
}
|
||||
|
||||
func (s *Service) BindToArgo(workflow *Workflow) error {
|
||||
service_manifest, err := yaml.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service_template := Template{Name: "workflow-service-pod",
|
||||
Resource: ServiceResource{
|
||||
Action: "create",
|
||||
SuccessCondition: "status.succeeded > 0",
|
||||
FailureCondition: "status.failed > 3",
|
||||
SetOwnerReference: true,
|
||||
Manifest: string(service_manifest),
|
||||
},
|
||||
}
|
||||
workflow.Spec.Templates = append(workflow.Spec.Templates, service_template)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
w "cloud.o-forge.io/core/oc-lib/models/workflow"
|
||||
"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/enum"
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
)
|
||||
@@ -37,12 +43,6 @@ func (c *Container) AddVolumeMount(volumeMount VolumeMount, volumes []VolumeMoun
|
||||
return volumes
|
||||
}
|
||||
|
||||
type VolumeMount struct {
|
||||
Name string `yaml:"name"`
|
||||
MountPath string `yaml:"mountPath"`
|
||||
Storage *resources.StorageResource `yaml:"-"`
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Name string `yaml:"name"`
|
||||
Template string `yaml:"template"`
|
||||
@@ -52,12 +52,77 @@ type Task struct {
|
||||
} `yaml:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
func NewTask(processingName string, graphItemID string) *Task {
|
||||
unique_name := GetArgoName(processingName, graphItemID)
|
||||
return &Task{
|
||||
Name: unique_name,
|
||||
Template: unique_name,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) BindToArgo(
|
||||
dag *Dag,
|
||||
graphItemID string,
|
||||
originWf *w.Workflow,
|
||||
processing *resources.ProcessingResource,
|
||||
firstItems, lastItems []string,
|
||||
) (*Dag, []string, []string) {
|
||||
if instance := processing.GetSelectedInstance(); instance != nil {
|
||||
t.addParams(instance.(*resources.ProcessingInstance).Env)
|
||||
t.addParams(instance.(*resources.ProcessingInstance).Inputs)
|
||||
t.addParams(instance.(*resources.ProcessingInstance).Outputs)
|
||||
}
|
||||
t.Dependencies = TransformDepsToArgo(originWf.GetDependencies(graphItemID))
|
||||
name := ""
|
||||
if originWf.Graph.Items[graphItemID].Processing != nil {
|
||||
name = originWf.Graph.Items[graphItemID].Processing.GetName()
|
||||
}
|
||||
if originWf.Graph.Items[graphItemID].Workflow != nil {
|
||||
name = originWf.Graph.Items[graphItemID].Workflow.GetName()
|
||||
}
|
||||
if len(t.Dependencies) == 0 && name != "" {
|
||||
firstItems = append(firstItems, GetArgoName(name, graphItemID))
|
||||
}
|
||||
if deps := originWf.IsDependancy(graphItemID); len(deps) == 0 && name != "" {
|
||||
lastItems = append(lastItems, GetArgoName(name, graphItemID))
|
||||
}
|
||||
dag.Tasks = append(dag.Tasks, *t)
|
||||
return dag, firstItems, lastItems
|
||||
}
|
||||
|
||||
func (t *Task) addParams(params []models.Param) {
|
||||
for _, value := range params {
|
||||
t.Arguments.Parameters = append(t.Arguments.Parameters, Parameter{
|
||||
Name: value.Name,
|
||||
Value: value.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) GetDeps(name string) (int, string) {
|
||||
for i, deps := range t.Dependencies {
|
||||
if strings.Contains(deps, name) {
|
||||
return i, deps
|
||||
}
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
type Dag struct {
|
||||
Tasks []Task `yaml:"tasks,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Dag) GetTask(taskName string) *Task {
|
||||
for _, task := range d.Tasks {
|
||||
if strings.Contains(task.Name, taskName) {
|
||||
return &task
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TemplateMetadata struct {
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
@@ -66,6 +131,10 @@ type Secret struct {
|
||||
Key string `yaml:"key"`
|
||||
}
|
||||
|
||||
func NewSecret(name string, key string) *Secret {
|
||||
return &Secret{Name: name, Key: key + "-key"}
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
Key string `yaml:"key"`
|
||||
Bucket string `yaml:"bucket"`
|
||||
@@ -81,6 +150,59 @@ type Artifact struct {
|
||||
S3 *Key `yaml:"s3,omitempty"`
|
||||
}
|
||||
|
||||
func NewArtifact(name string, rw graph.StorageProcessingGraphLink, params []models.Param, template Template) *Artifact {
|
||||
if rw.Write {
|
||||
name += "-" + rw.Destination + "-input-write"
|
||||
} else {
|
||||
name = "-" + rw.Destination + "-input-read"
|
||||
}
|
||||
return &Artifact{
|
||||
Name: name,
|
||||
Path: template.ReplacePerEnv(rw.Source, params),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) BindToArgo(storageType enum.StorageType, rw graph.StorageProcessingGraphLink, params []models.Param, template Template) {
|
||||
if rw.Write {
|
||||
template.Outputs.Artifacts = append(template.Inputs.Artifacts, *a)
|
||||
} else {
|
||||
template.Inputs.Artifacts = append(template.Outputs.Artifacts, *a)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) bindS3(rw graph.StorageProcessingGraphLink, params []models.Param, template Template) {
|
||||
a.S3 = &Key{
|
||||
Key: template.ReplacePerEnv(rw.Destination+"/"+rw.FileName, params),
|
||||
Insecure: true, // temporary
|
||||
}
|
||||
/* sel := storage.GetSelectedInstance()
|
||||
if sel != nil {
|
||||
if sel.(*resources.StorageResourceInstance).Credentials != nil {
|
||||
tool, err := tools2.NewService(conf.GetConfig().Mode)
|
||||
if err != nil || tool == nil {
|
||||
logger.Error().Msg("Could not create the access secret")
|
||||
} else {
|
||||
id, err := tool.CreateAccessSecret(namespace,
|
||||
sel.(*resources.StorageResourceInstance).Credentials.Login,
|
||||
sel.(*resources.StorageResourceInstance).Credentials.Pass)
|
||||
if err == nil {
|
||||
a.S3.AccessKeySecret = NewSecret(id, "access")
|
||||
a.S3.SecretKeySecret = NewSecret(id, "secret")
|
||||
}
|
||||
}
|
||||
}
|
||||
source := sel.(*resources.StorageResourceInstance).Source
|
||||
a.S3.Key = strings.ReplaceAll(strings.ReplaceAll(a.S3.Key, source+"/", ""), source, "")
|
||||
splits := strings.Split(a.S3.EndPoint, "/")
|
||||
if len(splits) > 1 {
|
||||
a.S3.Bucket = splits[0]
|
||||
a.S3.EndPoint = strings.Join(splits[1:], "/")
|
||||
} else {
|
||||
a.S3.Bucket = splits[0]
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
type InOut struct {
|
||||
Parameters []Parameter `yaml:"parameters"`
|
||||
Artifacts []Artifact `yaml:"artifacts,omitempty"`
|
||||
@@ -143,10 +265,43 @@ func (template *Template) ReplacePerEnv(arg string, envs []models.Param) string
|
||||
|
||||
// Add the metadata that allow Admiralty to pick up an Argo Workflow that needs to be reparted
|
||||
// The value of "clustername" is the peerId, which must be replaced by the node name's for this specific execution
|
||||
func (t *Template) AddAdmiraltyAnnotations(peerId string){
|
||||
func (t *Template) AddAdmiraltyAnnotations(peerID, namespace string) error {
|
||||
if t.Metadata.Annotations == nil {
|
||||
t.Metadata.Annotations = make(map[string]string)
|
||||
}
|
||||
t.Metadata.Annotations["multicluster.admiralty.io/elect"] = ""
|
||||
t.Metadata.Annotations["multicluster.admiralty.io/clustername"] = peerId
|
||||
}
|
||||
|
||||
const key = "admiralty.io/multi-cluster-scheduler"
|
||||
|
||||
var annotation SchedulerAnnotation
|
||||
|
||||
// Parse existing annotation if it exists
|
||||
if val, ok := t.Metadata.Annotations[key]; ok && val != "" {
|
||||
if err := json.Unmarshal([]byte(val), &annotation); err != nil {
|
||||
return fmt.Errorf("failed to parse existing scheduler annotation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add new affinity
|
||||
annotation.Affinities = append(annotation.Affinities, affinity{
|
||||
Cluster: "target-" + peerID + "-" + namespace,
|
||||
Namespace: namespace,
|
||||
})
|
||||
|
||||
// Encode back to JSON
|
||||
bytes, err := json.Marshal(annotation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode scheduler annotation: %w", err)
|
||||
}
|
||||
|
||||
t.Metadata.Annotations[key] = string(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
type affinity struct {
|
||||
Cluster string `json:"cluster"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
type SchedulerAnnotation struct {
|
||||
Affinities []affinity `json:"affinities"`
|
||||
}
|
||||
|
||||
92
models/utils.go
Normal file
92
models/utils.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
w "cloud.o-forge.io/core/oc-lib/models/workflow"
|
||||
)
|
||||
|
||||
type WorkflowsDependancies struct {
|
||||
FirstWfTasks map[string][]string
|
||||
RelatedWfTasks map[string][]string
|
||||
LastWfTasks map[string][]string
|
||||
}
|
||||
|
||||
func NewWorkflowDependancies() *WorkflowsDependancies {
|
||||
return &WorkflowsDependancies{
|
||||
FirstWfTasks: map[string][]string{},
|
||||
RelatedWfTasks: map[string][]string{},
|
||||
LastWfTasks: map[string][]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkflowsDependancies) BindFirstTasks(depsFunc func(v string) []w.Deps, dag *Dag) {
|
||||
for wfID, firstTasks := range w.FirstWfTasks {
|
||||
deps := depsFunc(wfID)
|
||||
if task := dag.GetTask(wfID); task != nil && len(deps) > 0 {
|
||||
task.Dependencies = append(task.Dependencies, firstTasks...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkflowsDependancies) BindRelatedTasks(dag *Dag) {
|
||||
for wfID, relatedWfTasks := range w.RelatedWfTasks {
|
||||
for _, dep := range relatedWfTasks {
|
||||
if task := dag.GetTask(dep); task != nil {
|
||||
index := -1
|
||||
if i, deps := task.GetDeps(wfID); deps != "" {
|
||||
index = i
|
||||
}
|
||||
if index != -1 {
|
||||
task.Dependencies = append(task.Dependencies[:index], task.Dependencies[index+1:]...)
|
||||
}
|
||||
if w.LastWfTasks[wfID] != nil {
|
||||
task.Dependencies = append(task.Dependencies, w.LastWfTasks[wfID]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Workflow struct {
|
||||
ApiVersion string `yaml:"apiVersion"`
|
||||
Kind string `yaml:"kind"`
|
||||
Metadata struct {
|
||||
Name string `yaml:"name"`
|
||||
} `yaml:"metadata"`
|
||||
Spec Spec `yaml:"spec,omitempty"`
|
||||
}
|
||||
|
||||
func (b *Workflow) GetDag() *Dag {
|
||||
for _, t := range b.Spec.Templates {
|
||||
if t.Name == "dag" {
|
||||
return t.Dag
|
||||
}
|
||||
}
|
||||
b.Spec.Templates = append(b.Spec.Templates, Template{Name: "dag", Dag: &Dag{}})
|
||||
return b.Spec.Templates[len(b.Spec.Templates)-1].Dag
|
||||
}
|
||||
|
||||
type Spec struct {
|
||||
ServiceAccountName string `yaml:"serviceAccountName"`
|
||||
Entrypoint string `yaml:"entrypoint"`
|
||||
Arguments []Parameter `yaml:"arguments,omitempty"`
|
||||
Volumes []VolumeClaimTemplate `yaml:"volumeClaimTemplates,omitempty"`
|
||||
Templates []Template `yaml:"templates"`
|
||||
Timeout int `yaml:"activeDeadlineSeconds,omitempty"`
|
||||
}
|
||||
|
||||
func GetArgoName(raw_name string, component_id string) (formatedName string) {
|
||||
formatedName = strings.ReplaceAll(raw_name, " ", "-")
|
||||
formatedName += "-" + component_id
|
||||
formatedName = strings.ToLower(formatedName)
|
||||
return
|
||||
}
|
||||
|
||||
func TransformDepsToArgo(deps []w.Deps) []string {
|
||||
argoDeps := []string{}
|
||||
for _, dep := range deps {
|
||||
argoDeps = append(argoDeps, GetArgoName(dep.Source, dep.Dest))
|
||||
}
|
||||
return argoDeps
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
)
|
||||
|
||||
type VolumeClaimTemplate struct {
|
||||
Metadata struct {
|
||||
Name string `yaml:"name"`
|
||||
@@ -15,3 +22,22 @@ type VolumeSpec struct {
|
||||
} `yaml:"requests"`
|
||||
} `yaml:"resources"`
|
||||
}
|
||||
|
||||
type VolumeMount struct {
|
||||
Name string `yaml:"name"`
|
||||
MountPath string `yaml:"mountPath"`
|
||||
Storage *resources.StorageResource `yaml:"-"`
|
||||
}
|
||||
|
||||
func (v *VolumeMount) BindToArgo(workflow *Workflow) { // TODO : one think about remote volume but TG
|
||||
index := 0
|
||||
if v.Storage.SelectedInstanceIndex != nil && (*v.Storage.SelectedInstanceIndex) >= 0 {
|
||||
index = *v.Storage.SelectedInstanceIndex
|
||||
}
|
||||
storage := v.Storage.Instances[index]
|
||||
new_volume := VolumeClaimTemplate{}
|
||||
new_volume.Metadata.Name = strings.ReplaceAll(strings.ToLower(v.Name), " ", "-")
|
||||
new_volume.Spec.AccessModes = []string{"ReadWriteOnce"}
|
||||
new_volume.Spec.Resources.Requests.Storage = fmt.Sprintf("%v", storage.SizeGB) + storage.SizeType.ToArgo()
|
||||
workflow.Spec.Volumes = append(workflow.Spec.Volumes, new_volume)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user