2026-02-23 18:10:47 +01:00
|
|
|
package infrastructure
|
|
|
|
|
|
|
|
|
|
import (
|
2026-02-25 09:04:48 +01:00
|
|
|
"context"
|
2026-02-23 18:10:47 +01:00
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2026-02-25 09:04:48 +01:00
|
|
|
"oc-scheduler/conf"
|
2026-02-23 18:10:47 +01:00
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
oclib "cloud.o-forge.io/core/oc-lib"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/config"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/models/utils"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/tools"
|
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// NATS emission
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func EmitNATS(peerID string, message tools.PropalgationMessage) {
|
2026-03-12 12:05:52 +01:00
|
|
|
// PB_CLOSE_PLANNER: notify local watchers so streams re-evaluate.
|
|
|
|
|
// Cache mutations (eviction or ownership reset) are the caller's
|
|
|
|
|
// responsibility — see evictAfter and ReleaseRefreshOwnership.
|
2026-02-23 18:10:47 +01:00
|
|
|
if message.Action == tools.PB_CLOSE_PLANNER {
|
2026-03-12 12:05:52 +01:00
|
|
|
notifyPlannerWatchers(peerID)
|
2026-02-23 18:10:47 +01:00
|
|
|
}
|
|
|
|
|
b, _ := json.Marshal(message)
|
|
|
|
|
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
|
|
|
|
FromApp: "oc-scheduler",
|
|
|
|
|
Datatype: -1,
|
|
|
|
|
Method: int(tools.PROPALGATION_EVENT),
|
|
|
|
|
Payload: b,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// NATS listeners
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func ListenNATS() {
|
|
|
|
|
tools.NewNATSCaller().ListenNats(map[tools.NATSMethod]func(tools.NATSResponse){
|
2026-03-17 11:58:27 +01:00
|
|
|
tools.PLANNER_EXECUTION: handlePlannerExecution,
|
|
|
|
|
tools.PROPALGATION_EVENT: handlePropagationEvent,
|
|
|
|
|
tools.REMOVE_RESOURCE: handleRemoveResource,
|
|
|
|
|
tools.CREATE_RESOURCE: handleCreateResource,
|
2026-02-23 18:10:47 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Confirm channels
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// ListenConfirm opens a direct NATS connection and subscribes to the hardcoded
|
|
|
|
|
// "confirm_booking" and "confirm_purchase" subjects. It reconnects automatically
|
|
|
|
|
// if the connection is lost.
|
|
|
|
|
func ListenConfirm() {
|
|
|
|
|
natsURL := config.GetConfig().NATSUrl
|
|
|
|
|
if natsURL == "" {
|
|
|
|
|
fmt.Println("ListenConfirm: NATS_SERVER not set, skipping confirm listeners")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
nc, err := nats.Connect(natsURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println("ListenConfirm: could not connect to NATS:", err)
|
|
|
|
|
time.Sleep(time.Minute)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
wg.Add(2)
|
|
|
|
|
go listenConfirmChannel(nc, "confirm_booking", tools.BOOKING, &wg)
|
|
|
|
|
go listenConfirmChannel(nc, "confirm_purchase", tools.PURCHASE_RESOURCE, &wg)
|
|
|
|
|
wg.Wait()
|
|
|
|
|
nc.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-17 11:58:27 +01:00
|
|
|
// Draft timeout
|
2026-02-23 18:10:47 +01:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-17 11:58:27 +01:00
|
|
|
// draftTimeout deletes a booking or purchase resource if it is still a draft
|
|
|
|
|
// after the 10-minute confirmation window has elapsed.
|
|
|
|
|
func draftTimeout(id string, dt tools.DataType) {
|
|
|
|
|
adminReq := &tools.APIRequest{Admin: true}
|
|
|
|
|
var res utils.DBObject
|
|
|
|
|
var loadErr error
|
|
|
|
|
switch dt {
|
|
|
|
|
case tools.BOOKING:
|
|
|
|
|
res, _, loadErr = booking.NewAccessor(adminReq).LoadOne(id)
|
|
|
|
|
case tools.PURCHASE_RESOURCE:
|
|
|
|
|
res, _, loadErr = purchase_resource.NewAccessor(adminReq).LoadOne(id)
|
|
|
|
|
default:
|
2026-02-23 18:10:47 +01:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-17 11:58:27 +01:00
|
|
|
if loadErr != nil || res == nil || !res.IsDrafted() {
|
2026-02-23 18:10:47 +01:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-17 11:58:27 +01:00
|
|
|
switch dt {
|
|
|
|
|
case tools.BOOKING:
|
|
|
|
|
booking.NewAccessor(adminReq).DeleteOne(id)
|
|
|
|
|
case tools.PURCHASE_RESOURCE:
|
|
|
|
|
purchase_resource.NewAccessor(adminReq).DeleteOne(id)
|
2026-02-23 18:10:47 +01:00
|
|
|
}
|
2026-03-17 11:58:27 +01:00
|
|
|
fmt.Printf("draftTimeout: %s %s deleted (still draft after 10 min)\n", dt.String(), id)
|
2026-02-23 18:10:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-17 11:58:27 +01:00
|
|
|
// Kubernetes namespace helper
|
2026-02-23 18:10:47 +01:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-02-25 09:04:48 +01:00
|
|
|
func createNamespace(ns string) error {
|
|
|
|
|
/*
|
|
|
|
|
* This function is used to create a namespace.
|
|
|
|
|
* It takes the following parameters:
|
|
|
|
|
* - ns: the namespace you want to create
|
|
|
|
|
*/
|
|
|
|
|
logger := oclib.GetLogger()
|
|
|
|
|
serv, err := tools.NewKubernetesService(
|
|
|
|
|
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort, conf.GetConfig().KubeCA,
|
|
|
|
|
conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
c := context.Background()
|
|
|
|
|
|
|
|
|
|
ok, err := serv.GetNamespace(c, ns)
|
|
|
|
|
if ok != nil && err == nil {
|
|
|
|
|
logger.Debug().Msg("A namespace with name " + ns + " already exists")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = serv.CreateNamespace(c, ns)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = serv.CreateServiceAccount(c, ns)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
role := "argo-role"
|
|
|
|
|
err = serv.CreateRole(c, ns, role,
|
|
|
|
|
[][]string{
|
|
|
|
|
{"coordination.k8s.io"},
|
|
|
|
|
{""},
|
|
|
|
|
{""}},
|
|
|
|
|
[][]string{
|
|
|
|
|
{"leases"},
|
|
|
|
|
{"secrets"},
|
|
|
|
|
{"pods"}},
|
|
|
|
|
[][]string{
|
|
|
|
|
{"get", "create", "update"},
|
|
|
|
|
{"get"},
|
|
|
|
|
{"patch"}})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return serv.CreateRoleBinding(c, ns, "argo-role-binding", role)
|
|
|
|
|
}
|