@@ -33,6 +33,7 @@ import (
3333 "sync"
3434 "time"
3535
36+ "github.com/AliceO2Group/Control/common/controlmode"
3637 "github.com/AliceO2Group/Control/common/utils/safeacks"
3738
3839 "github.com/AliceO2Group/Control/apricot"
@@ -51,6 +52,7 @@ import (
5152 "github.com/spf13/viper"
5253 "gopkg.in/yaml.v3"
5354
55+ k8sclient "github.com/AliceO2Group/Control/control-operator/pkg/client"
5456 "github.com/AliceO2Group/Control/core/controlcommands"
5557 "github.com/AliceO2Group/Control/core/task/channel"
5658 "github.com/k0kubun/pp"
@@ -103,6 +105,9 @@ type Manager struct {
103105 internalEventCh chan <- event.Event
104106 ackKilledTasks * safeacks.SafeAcks
105107 killTasksMu sync.Mutex // to avoid races when attempting to kill the same tasks in different goroutines
108+
109+ k8sClient * k8sclient.Client
110+ k8sEnvs * k8sEnvRegistry
106111}
107112
108113func NewManager (shutdown func (), internalEventCh chan <- event.Event ) (taskman * Manager , err error ) {
@@ -144,6 +149,13 @@ func NewManager(shutdown func(), internalEventCh chan<- event.Event) (taskman *M
144149 taskman .tasksToDeploy = taskman .schedulerState .tasksToDeploy
145150 taskman .reviveOffersTrg = taskman .schedulerState .reviveOffersTrg
146151 taskman .ackKilledTasks = safeacks .NewAcks ()
152+ taskman .k8sEnvs = newK8sEnvRegistry ()
153+ if k8sClient , k8sErr := newK8sClientFromViper (); k8sErr != nil {
154+ log .WithError (k8sErr ).Warn ("K8s client init failed, K8s tasks disabled" )
155+ } else {
156+ log .Info ("K8s client initiated" )
157+ taskman .k8sClient = k8sClient
158+ }
147159
148160 schedState .setupCli ()
149161
@@ -476,6 +488,33 @@ func (m *Manager) acquireTasks(envId uid.ID, taskDescriptors Descriptors) (err e
476488 tasksToRun = append (tasksToRun , taskDescriptors ... )
477489 }
478490
491+ // Split K8s descriptors out before the Mesos retry loop.
492+ var k8sDescriptors Descriptors
493+ {
494+ mesos := make (Descriptors , 0 , len (tasksToRun ))
495+ for _ , desc := range tasksToRun {
496+ class , ok := m .classes .GetClass (desc .TaskClassName )
497+ if ok && class != nil && (class .Control .Mode == controlmode .KUBERNETES_DIRECT ||
498+ class .Control .Mode == controlmode .KUBERNETES_FAIRMQ ) {
499+ k8sDescriptors = append (k8sDescriptors , desc )
500+ } else {
501+ mesos = append (mesos , desc )
502+ }
503+ }
504+ tasksToRun = mesos
505+ }
506+
507+ var k8sDeployed DeploymentMap
508+ var k8sDeployErr error
509+ if len (k8sDescriptors ) > 0 {
510+ if m .k8sClient == nil {
511+ k8sDeployErr = fmt .Errorf ("K8s client not configured" )
512+ } else {
513+ // TODO: this is synchronous call it would be worth it to make it async, so mesos loop doesn't have to wait for k8s
514+ k8sDeployed , k8sDeployErr = m .deployKubernetesTasks (context .Background (), envId , k8sDescriptors )
515+ }
516+ }
517+
479518 // TODO: fill out tasksToTeardown in the previous loop
480519 //if len(tasksToTeardown) > 0 {
481520 // err = m.TeardownTasks(tasksToTeardown)
@@ -617,6 +656,32 @@ func (m *Manager) acquireTasks(envId uid.ID, taskDescriptors Descriptors) (err e
617656 }
618657 }
619658
659+ // Merge K8s deployment results into unified tracking.
660+ if k8sDeployErr != nil {
661+ logWithId .WithError (k8sDeployErr ).Error ("K8s task deployment failed" )
662+ for _ , desc := range k8sDescriptors {
663+ if desc .TaskRole .GetTaskTraits ().Critical {
664+ deploymentSuccess = false
665+ undeployableCriticalDescriptors = append (undeployableCriticalDescriptors , desc )
666+ undeployableDescriptors = append (undeployableDescriptors , desc )
667+ } else {
668+ undeployableNonCriticalDescriptors = append (undeployableNonCriticalDescriptors , desc )
669+ undeployableDescriptors = append (undeployableDescriptors , desc )
670+ }
671+ }
672+ } else {
673+ logWithId .Infof ("succeeded to deploy %d kubernetes tasks" , len (k8sDeployed ))
674+ for taskPtr , desc := range k8sDeployed {
675+ taskPtr .SetParent (desc .TaskRole )
676+ if ! taskPtr .IsLocked () {
677+ log .WithField ("task" , taskPtr .taskId ).Warning ("K8s task not locked after deployment" )
678+ deploymentSuccess = false
679+ }
680+ allDeployedTasks [taskPtr ] = desc
681+ desc .TaskRole .UpdateStatus (ACTIVE )
682+ }
683+ }
684+
620685 log .Infof ("Succeeded to deploy %d/%d tasks" , len (allDeployedTasks ), len (tasksToRun ))
621686
622687 {
@@ -667,6 +732,13 @@ func (m *Manager) acquireTasks(envId uid.ID, taskDescriptors Descriptors) (err e
667732}
668733
669734func (m * Manager ) releaseTasks (envId uid.ID , tasks Tasks ) {
735+ if m .k8sClient != nil {
736+ if err := m .killK8sEnvironment (context .Background (), envId ); err != nil {
737+ log .WithError (err ).WithField ("partition" , envId .String ()).
738+ Warn ("failed to delete K8s Environment CRD during release" )
739+ }
740+ }
741+
670742 taskReleaseErrors := make (map [string ]error )
671743 taskIdsReleased := make ([]string , 0 )
672744
@@ -701,18 +773,21 @@ func (m *Manager) releaseTask(envId uid.ID, task *Task) error {
701773}
702774
703775func (m * Manager ) configureTasks (envId uid.ID , tasks Tasks ) error {
704- notify := make (chan controlcommands.MesosCommandResponse )
705- receivers , err := tasks .GetMesosCommandTargets ()
706- if err != nil {
707- return err
708- }
709-
710- if tasks == nil || len (tasks ) == 0 {
711- return fmt .Errorf ("empty task list to configure for environment %s" , envId .String ())
776+ var k8sTasks Tasks
777+ var mesosTasks Tasks
778+ for _ , t := range tasks {
779+ if t .GetControlMode () == controlmode .KUBERNETES_DIRECT ||
780+ t .GetControlMode () == controlmode .KUBERNETES_FAIRMQ {
781+ k8sTasks = append (k8sTasks , t )
782+ } else {
783+ mesosTasks = append (mesosTasks , t )
784+ }
712785 }
713786
714787 // We fetch each task's local bindMap to generate a global bindMap for the whole Tasks slice,
715788 // i.e. a map of the paths of registered inbound channels and their ports.
789+ // We use the full task list (including K8s tasks) so that Mesos tasks can resolve
790+ // connect targets that point at K8s task channels.
716791 bindMap := make (channel.BindMap )
717792 for _ , task := range tasks {
718793 if task .GetParent () == nil { // Crash reported here by Roberto 6/2022
@@ -743,11 +818,27 @@ func (m *Manager) configureTasks(envId uid.ID, tasks Tasks) error {
743818 log .WithFields (logrus.Fields {"bindMap" : pp .Sprint (bindMap ), "envId" : envId .String ()}).
744819 Debug ("generated inbound bindMap for environment configuration" )
745820
821+ if len (k8sTasks ) > 0 && m .k8sClient != nil {
822+ if err := m .configureK8sTasks (context .Background (), envId , k8sTasks , bindMap ); err != nil {
823+ return err
824+ }
825+ }
826+
827+ if len (mesosTasks ) == 0 {
828+ return nil
829+ }
830+
831+ notify := make (chan controlcommands.MesosCommandResponse )
832+ receivers , err := mesosTasks .GetMesosCommandTargets ()
833+ if err != nil {
834+ return err
835+ }
836+
746837 src := sm .STANDBY .String ()
747838 evt := "CONFIGURE"
748839 dest := sm .CONFIGURED .String ()
749840 args := make (controlcommands.PropertyMapsMap )
750- args , err = tasks .BuildPropertyMaps (bindMap )
841+ args , err = mesosTasks .BuildPropertyMaps (bindMap )
751842 if err != nil {
752843 return err
753844 }
@@ -811,6 +902,28 @@ func (m *Manager) configureTasks(envId uid.ID, tasks Tasks) error {
811902}
812903
813904func (m * Manager ) transitionTasks (envId uid.ID , tasks Tasks , src string , event string , dest string , commonArgs controlcommands.PropertyMap ) error {
905+ var mesosTasks Tasks
906+
907+ for _ , t := range tasks {
908+ if t .GetControlMode () != controlmode .KUBERNETES_DIRECT &&
909+ t .GetControlMode () != controlmode .KUBERNETES_FAIRMQ {
910+ mesosTasks = append (mesosTasks , t )
911+ }
912+ }
913+
914+ if len (mesosTasks ) == 0 {
915+ return nil
916+ }
917+
918+ if len (tasks ) != len (mesosTasks ) {
919+ log .WithField ("partition" , envId ).Infof ("Transitioning k8s environment from %s, to %s, event: %s" , src , dest , event )
920+ if err := m .transitionAndWaitK8sEnvState (context .Background (), envId , strings .ToLower (dest )); err != nil {
921+ return err
922+ }
923+ }
924+
925+ tasks = mesosTasks
926+
814927 notify := make (chan controlcommands.MesosCommandResponse )
815928 receivers , err := tasks .GetMesosCommandTargets ()
816929 if err != nil {
@@ -1128,6 +1241,17 @@ func (m *Manager) KillTasks(taskIds []string) (killed Tasks, running Tasks, err
11281241}
11291242
11301243func (m * Manager ) doKillTask (task * Task ) error {
1244+ if task .GetControlMode () == controlmode .KUBERNETES_DIRECT ||
1245+ task .GetControlMode () == controlmode .KUBERNETES_FAIRMQ {
1246+ if m .k8sClient != nil {
1247+ if err := m .killK8sTask (context .Background (), task ); err != nil {
1248+ return err
1249+ }
1250+ }
1251+ // K8s delete is synchronous — send the ack immediately so KillTasks doesn't block.
1252+ _ = m .ackKilledTasks .TrySendAck (task .taskId )
1253+ return nil
1254+ }
11311255 return m .schedulerState .killTask (context .TODO (), task .GetMesosCommandTarget ())
11321256}
11331257
@@ -1178,6 +1302,10 @@ func (m *Manager) Start(ctx context.Context) {
11781302
11791303 m .schedulerState .Start (ctx )
11801304
1305+ if m .k8sClient != nil {
1306+ go m .watchK8sTasks (ctx )
1307+ }
1308+
11811309 go func () {
11821310 for {
11831311 select {
0 commit comments