2024-08-02 13:34:39 +02:00
// A class that translates the informations held in the graph object
// via its lists of components into an argo file, using the a list of
// link ID to build the dag
package workflow_builder
import (
2025-06-12 14:01:16 +02:00
"errors"
2024-09-25 15:46:39 +02:00
"fmt"
2025-02-14 12:00:29 +01:00
"oc-monitord/conf"
2025-06-12 14:01:16 +02:00
"oc-monitord/models"
2024-08-19 11:43:40 +02:00
. "oc-monitord/models"
2025-04-29 12:23:44 +02:00
"os"
"time"
2024-08-02 13:34:39 +02:00
2024-08-19 11:43:40 +02:00
oclib "cloud.o-forge.io/core/oc-lib"
2025-04-08 17:22:43 +02:00
"cloud.o-forge.io/core/oc-lib/logs"
2025-01-13 14:05:21 +01:00
"cloud.o-forge.io/core/oc-lib/models/resources"
2024-08-29 09:34:10 +02:00
w "cloud.o-forge.io/core/oc-lib/models/workflow"
2025-04-29 12:23:44 +02:00
"github.com/nwtgck/go-fakelish"
2024-09-03 14:24:03 +02:00
"github.com/rs/zerolog"
2025-04-29 12:23:44 +02:00
"gopkg.in/yaml.v3"
2024-08-02 13:34:39 +02:00
)
2024-09-03 14:24:03 +02:00
var logger zerolog . Logger
2024-08-02 13:34:39 +02:00
type ArgoBuilder struct {
2025-04-28 14:01:57 +02:00
OriginWorkflow * w . Workflow
2025-06-12 14:01:16 +02:00
Workflow * models . Workflow
2025-04-28 14:01:57 +02:00
Services [ ] * Service
Timeout int
RemotePeers [ ] string
2024-08-02 13:34:39 +02:00
}
2025-02-05 08:36:26 +01:00
// TODO: found on a processing instance linked to storage
// add s3, gcs, azure, etc if needed on a link between processing and storage
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) CreateDAG ( namespace string , write bool ) ( int , [ ] string , [ ] string , error ) {
2025-04-08 17:22:43 +02:00
logger = logs . GetLogger ( )
2025-04-30 17:51:24 +02:00
logger . Info ( ) . Msg ( fmt . Sprint ( "Creating DAG " , b . OriginWorkflow . Graph . Items ) )
2024-08-29 10:17:31 +02:00
// handle services by checking if there is only one processing with hostname and port
2025-02-14 12:00:29 +01:00
firstItems , lastItems , volumes := b . createTemplates ( namespace )
2024-10-11 13:44:16 +02:00
b . createVolumes ( volumes )
2024-08-22 10:52:49 +02:00
if b . Timeout > 0 {
b . Workflow . Spec . Timeout = b . Timeout
}
2025-06-12 14:01:16 +02:00
b . Workflow . Spec . ServiceAccountName = "sa-" + namespace
2024-08-02 13:34:39 +02:00
b . Workflow . Spec . Entrypoint = "dag"
b . Workflow . ApiVersion = "argoproj.io/v1alpha1"
b . Workflow . Kind = "Workflow"
2025-06-12 14:01:16 +02:00
return len ( b . Workflow . GetDag ( ) . Tasks ) , firstItems , lastItems , nil
2024-08-02 13:34:39 +02:00
}
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) createTemplates ( namespace string ) ( [ ] string , [ ] string , [ ] models . VolumeMount ) {
volumes := [ ] models . VolumeMount { }
2024-10-11 13:44:16 +02:00
firstItems := [ ] string { }
lastItems := [ ] string { }
2025-02-05 08:36:26 +01:00
items := b . OriginWorkflow . GetGraphItems ( b . OriginWorkflow . Graph . IsProcessing )
2025-04-30 17:51:24 +02:00
logger . Info ( ) . Msg ( fmt . Sprint ( "Creating templates" , len ( items ) ) )
2025-06-12 14:01:16 +02:00
for _ , item := range items {
2025-02-05 08:36:26 +01:00
instance := item . Processing . GetSelectedInstance ( )
2025-04-30 17:51:24 +02:00
logger . Info ( ) . Msg ( fmt . Sprint ( "Creating template for" , item . Processing . GetName ( ) , instance ) )
2025-02-05 08:36:26 +01:00
if instance == nil || instance . ( * resources . ProcessingInstance ) . Access == nil && instance . ( * resources . ProcessingInstance ) . Access . Container != nil {
logger . Error ( ) . Msg ( "Not enough configuration setup, template can't be created : " + item . Processing . GetName ( ) )
2024-10-11 13:44:16 +02:00
return firstItems , lastItems , volumes
2024-08-02 13:34:39 +02:00
}
2025-02-14 12:00:29 +01:00
volumes , firstItems , lastItems = b . createArgoTemplates ( namespace ,
2025-02-05 08:36:26 +01:00
item . ID , item . Processing , volumes , firstItems , lastItems )
2024-10-11 13:44:16 +02:00
}
2025-06-12 14:01:16 +02:00
wfDeps := models . NewWorkflowDependancies ( )
for _ , workflowID := range b . OriginWorkflow . Workflows {
b . createWorkflowArgoTemplate ( workflowID , namespace , wfDeps )
2024-08-02 13:34:39 +02:00
}
2025-06-12 14:01:16 +02:00
wfDeps . BindRelatedTasks ( b . Workflow . GetDag ( ) )
wfDeps . BindFirstTasks ( b . OriginWorkflow . GetDependencies , b . Workflow . GetDag ( ) )
2024-09-03 14:24:03 +02:00
if b . Services != nil {
2025-06-12 14:01:16 +02:00
dag := b . Workflow . GetDag ( )
2024-10-11 13:44:16 +02:00
dag . Tasks = append ( dag . Tasks , Task { Name : "workflow-service-pod" , Template : "workflow-service-pod" } )
b . addServiceToArgo ( )
2024-09-03 14:24:03 +02:00
}
2024-10-11 13:44:16 +02:00
return firstItems , lastItems , volumes
2024-08-02 13:34:39 +02:00
}
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) createWorkflowArgoTemplate (
workflowID string ,
namespace string ,
wfDeps * models . WorkflowsDependancies ,
) {
realWorkflow , code , err := w . NewAccessor ( nil ) . LoadOne ( workflowID )
if code != 200 {
logger . Error ( ) . Msg ( "Error loading the workflow : " + err . Error ( ) )
return
}
subBuilder := ArgoBuilder { OriginWorkflow : realWorkflow . ( * w . Workflow ) , Workflow : & models . Workflow { } , Timeout : b . Timeout }
_ , fi , li , err := subBuilder . CreateDAG ( namespace , false )
if err != nil {
logger . Error ( ) . Msg ( "Error creating the subworkflow : " + err . Error ( ) )
return
}
wfDeps . FirstWfTasks [ workflowID ] = fi
if depsOfIds := subBuilder . OriginWorkflow . IsDependancy ( workflowID ) ; len ( depsOfIds ) > 0 { // IS BEFORE
wfDeps . LastWfTasks [ workflowID ] = li
wfDeps . RelatedWfTasks [ workflowID ] = models . TransformDepsToArgo ( depsOfIds )
}
subDag := subBuilder . Workflow . GetDag ( )
d := b . Workflow . GetDag ( )
d . Tasks = append ( d . Tasks , subDag . Tasks ... ) // add the tasks of the subworkflow to the main workflow
b . Workflow . Spec . Templates = append ( b . Workflow . Spec . Templates , subBuilder . Workflow . Spec . Templates ... )
b . Workflow . Spec . Volumes = append ( b . Workflow . Spec . Volumes , subBuilder . Workflow . Spec . Volumes ... )
b . Workflow . Spec . Arguments = append ( b . Workflow . Spec . Arguments , subBuilder . Workflow . Spec . Arguments ... )
b . Services = append ( b . Services , subBuilder . Services ... )
}
func ( b * ArgoBuilder ) createArgoTemplates (
namespace string ,
2025-02-14 12:00:29 +01:00
id string ,
2025-01-13 14:05:21 +01:00
processing * resources . ProcessingResource ,
2025-06-12 14:01:16 +02:00
volumes [ ] models . VolumeMount ,
2024-10-11 13:44:16 +02:00
firstItems [ ] string ,
2025-06-12 14:01:16 +02:00
lastItems [ ] string ,
) ( [ ] models . VolumeMount , [ ] string , [ ] string ) {
_ , firstItems , lastItems = NewTask ( processing . Name , id ) . BindToArgo ( b . Workflow . GetDag ( ) , id , b . OriginWorkflow , processing , firstItems , lastItems )
template := & Template { Name : models . GetArgoName ( processing . GetName ( ) , id ) }
2025-04-30 17:51:24 +02:00
logger . Info ( ) . Msg ( fmt . Sprint ( "Creating template for" , template . Name ) )
2025-06-12 14:01:16 +02:00
template . CreateContainer ( processing , b . Workflow . GetDag ( ) )
if err := b . RepartiteProcess ( * processing , id , template , namespace ) ; err != nil {
2025-06-12 14:02:07 +02:00
logger . Error ( ) . Msg ( fmt . Sprint ( "Problem to sets up repartition expected %v" , err . Error ( ) ) )
2025-06-12 14:01:16 +02:00
return volumes , firstItems , lastItems
2025-03-07 16:19:26 +01:00
}
2024-11-07 13:36:28 +01:00
// get datacenter from the processing
2024-11-12 09:10:33 +01:00
if processing . IsService {
2024-10-11 13:44:16 +02:00
b . CreateService ( id , processing )
template . Metadata . Labels = make ( map [ string ] string )
2024-11-12 09:10:33 +01:00
template . Metadata . Labels [ "app" ] = "oc-service-" + processing . GetName ( ) // Construct the template for the k8s service and add a link in graph between k8s service and processing
2024-10-11 13:44:16 +02:00
}
b . Workflow . Spec . Templates = append ( b . Workflow . Spec . Templates , * template )
return volumes , firstItems , lastItems
2024-08-02 13:34:39 +02:00
}
2025-02-28 14:15:59 +01:00
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) createVolumes ( volumes [ ] models . VolumeMount ) { // TODO : one think about remote volume but TG
2024-10-11 13:44:16 +02:00
for _ , volume := range volumes {
2025-06-12 14:01:16 +02:00
volume . BindToArgo ( b . Workflow )
2024-08-02 13:34:39 +02:00
}
2024-10-11 13:44:16 +02:00
}
2024-08-02 13:34:39 +02:00
2025-02-28 14:15:59 +01:00
// Verify if a processing resource is attached to another Compute than the one hosting
2025-03-07 16:19:26 +01:00
// the current Open Cloud instance. If true return the peer ID to contact
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) RepartiteProcess ( processing resources . ProcessingResource , graphID string , template * models . Template , namespace string ) error {
computeAttached := b . OriginWorkflow . GetByRelatedProcessing ( processing . GetID ( ) , b . OriginWorkflow . Graph . IsCompute )
if len ( computeAttached ) == 0 {
return errors . New ( "No compute was found attached to processing " + processing . Name + " : " + processing . UUID )
2025-03-07 16:19:26 +01:00
}
2025-04-28 14:01:57 +02:00
// Creates an accessor srtictly for Peer Collection
2025-06-12 14:01:16 +02:00
for _ , related := range computeAttached {
res := oclib . NewRequest ( oclib . LibDataEnum ( oclib . PEER ) , "" , "" , nil , nil ) . LoadOne ( related . Node . GetCreatorID ( ) )
if res . Err != "" {
return errors . New ( res . Err )
}
2025-04-28 14:01:57 +02:00
2025-06-12 14:01:16 +02:00
peer := * res . ToPeer ( )
2025-02-28 14:15:59 +01:00
2025-06-12 14:01:16 +02:00
isNotReparted := peer . State == 1
logger . Info ( ) . Msg ( fmt . Sprint ( "Result IsMySelf for " , peer . UUID , " : " , isNotReparted ) )
if ! ( isNotReparted ) {
logger . Debug ( ) . Msg ( "Reparted processing, on " + peer . UUID )
b . RemotePeers = append ( b . RemotePeers , peer . UUID )
template . AddAdmiraltyAnnotations ( peer . UUID , namespace )
2025-02-28 14:15:59 +01:00
}
}
2025-04-28 14:01:57 +02:00
return nil
}
2025-02-28 14:15:59 +01:00
2025-03-07 16:19:26 +01:00
// Execute the last actions once the YAML file for the Argo Workflow is created
2025-06-12 14:01:16 +02:00
func ( b * ArgoBuilder ) CompleteBuild ( namespace string ) ( string , error ) {
logger . Info ( ) . Msg ( "DEV :: Completing build" )
setter := AdmiraltySetter { Id : namespace }
2025-04-02 11:40:14 +02:00
// Setup admiralty for each node
2025-03-07 16:19:26 +01:00
for _ , peer := range b . RemotePeers {
2025-04-30 17:51:24 +02:00
logger . Info ( ) . Msg ( fmt . Sprint ( "DEV :: Launching Admiralty Setup for " , peer ) )
2025-06-12 14:01:16 +02:00
setter . InitializeAdmiralty ( conf . GetConfig ( ) . PeerID , peer )
2025-04-29 12:23:44 +02:00
}
// Generate the YAML file
random_name := fakelish . GenerateFakeWord ( 5 , 8 ) + "-" + fakelish . GenerateFakeWord ( 5 , 8 )
b . Workflow . Metadata . Name = "oc-monitor-" + random_name
logger = oclib . GetLogger ( )
yamlified , err := yaml . Marshal ( b . Workflow )
if err != nil {
logger . Error ( ) . Msg ( "Could not transform object to yaml file" )
2025-06-12 14:01:16 +02:00
return "" , err
2025-04-29 12:23:44 +02:00
}
// Give a unique name to each argo file with its timestamp DD:MM:YYYY_hhmmss
current_timestamp := time . Now ( ) . Format ( "02_01_2006_150405" )
file_name := random_name + "_" + current_timestamp + ".yml"
workflows_dir := "./argo_workflows/"
err = os . WriteFile ( workflows_dir + file_name , [ ] byte ( yamlified ) , 0660 )
2025-06-12 14:01:16 +02:00
2025-04-29 12:23:44 +02:00
if err != nil {
logger . Error ( ) . Msg ( "Could not write the yaml file" )
return "" , err
}
return workflows_dir + file_name , nil
2025-04-28 14:01:57 +02:00
}