Scheduling CreateNamespace

This commit is contained in:
mr
2026-03-19 09:25:46 +01:00
parent 34be86b244
commit 7cbe08f4ea
11 changed files with 96 additions and 78 deletions

View File

@@ -72,7 +72,7 @@ func (ws *WorkflowSchedule) Check(wfID string, asap bool, preemption bool, reque
// 2. Resolve start
start := ws.Start
if asap || start.IsZero() {
start = time.Now().Add(asapBuffer)
start = time.Now().UTC().Add(asapBuffer)
}
// 3. Resolve end use explicit end/duration or estimate via Planify

View File

@@ -104,7 +104,7 @@ func handleCreateBooking(bk *booking.Booking, self *peer.Peer, adminReq *tools.A
return
}
// Expired check only on confirmation (IsDraft→false).
if !bk.IsDraft && !prev.ExpectedStartDate.IsZero() && prev.ExpectedStartDate.Before(time.Now()) {
if !bk.IsDraft && !prev.ExpectedStartDate.IsZero() && prev.ExpectedStartDate.Before(time.Now().UTC()) {
fmt.Println("ListenNATS CREATE_RESOURCE booking: expired, deleting", bk.GetID())
booking.NewAccessor(adminReq).DeleteOne(bk.GetID())
return
@@ -120,7 +120,7 @@ func handleCreateBooking(bk *booking.Booking, self *peer.Peer, adminReq *tools.A
return
}
// New booking: standard create flow.
if !bk.ExpectedStartDate.IsZero() && bk.ExpectedStartDate.Before(time.Now()) {
if !bk.ExpectedStartDate.IsZero() && bk.ExpectedStartDate.Before(time.Now().UTC()) {
fmt.Println("ListenNATS: booking start date is in the past, discarding")
return
}

View File

@@ -153,7 +153,7 @@ func storePlanner(peerID string, p *planner.Planner) {
if isNew {
entry = &plannerEntry{}
PlannerCache[peerID] = entry
plannerAddedAt[peerID] = time.Now()
plannerAddedAt[peerID] = time.Now().UTC()
go evictAfter(peerID, plannerTTL)
}
entry.Planner = p
@@ -196,7 +196,7 @@ func RequestPlannerRefresh(peerIDs []string, executionsID string) []string {
if entry == nil {
entry = &plannerEntry{}
PlannerCache[peerID] = entry
plannerAddedAt[peerID] = time.Now()
plannerAddedAt[peerID] = time.Now().UTC()
go evictAfter(peerID, plannerTTL)
}
shouldRequest := !entry.Refreshing

View File

@@ -55,17 +55,17 @@ type WorkflowSchedule struct {
func NewScheduler(mode int, start string, end string, durationInS float64, cron string) *WorkflowSchedule {
ws := &WorkflowSchedule{
UUID: uuid.New().String(),
Start: time.Now().Add(asapBuffer),
Start: time.Now().UTC().Add(asapBuffer),
BookingMode: booking.BookingMode(mode),
DurationS: durationInS,
Cron: cron,
}
s, err := time.Parse("2006-01-02T15:04:05", start)
s, err := time.ParseInLocation("2006-01-02T15:04:05", start, time.UTC)
if err == nil && ws.BookingMode == booking.PLANNED {
ws.Start = s // can apply a defined start other than now, if planned
}
e, err := time.Parse("2006-01-02T15:04:05", end)
e, err := time.ParseInLocation("2006-01-02T15:04:05", end, time.UTC)
if err == nil {
ws.End = &e
}
@@ -153,7 +153,7 @@ func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*
// Obsolescence check: abort if any session execution's start date has passed.
executions := loadSessionExecs(ws.UUID)
for _, exec := range executions {
if !exec.ExecDate.IsZero() && exec.ExecDate.Before(time.Now()) {
if !exec.ExecDate.IsZero() && exec.ExecDate.Before(time.Now().UTC()) {
return ws, nil, nil, fmt.Errorf("execution %s is obsolete (start date in the past)", exec.GetID())
}
}
@@ -163,7 +163,7 @@ func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*
}
for _, exec := range executions {
go WatchExecDeadline(exec.GetID(), exec.ExecDate, selfID, request)
go WatchExecDeadline(exec.GetID(), exec.ExecutionsID, exec.ExecDate, selfID, request)
}
obj, _, _ := workflow.NewAccessor(request).LoadOne(wfID)

View File

@@ -1,8 +1,10 @@
package infrastructure
import (
"context"
"encoding/json"
"fmt"
"oc-scheduler/conf"
"oc-scheduler/infrastructure/scheduling"
"time"
@@ -119,7 +121,8 @@ func (ws *WorkflowSchedule) UpsertSessionDrafts(wfID, executionsID string, selfI
ex, _, err := utils.GenericStoreOne(exec, workflow_execution.NewAccessor(adminReq))
if err == nil {
RegisterExecLock(ex.GetID())
go WatchExecDeadline(ex.GetID(), exec.ExecDate, selfID, request)
go WatchExecDeadline(
ex.GetID(), executionsID, exec.ExecDate, selfID, request)
}
}
@@ -269,24 +272,71 @@ func emitNATSRemove(id, schedulerPeerID, executionsID string, dt tools.DataType)
// Deadline watchers
// ---------------------------------------------------------------------------
// WatchExecDeadline purges all unconfirmed bookings/purchases for an execution
// one minute before its scheduled start, to avoid stale drafts blocking resources.
// WatchExecDeadline fires one minute before the execution start date.
// If the execution is still a draft it is purged; otherwise the namespace
// is created and a WatchExecEnd watcher is armed.
// If the deadline has already passed (e.g. after a process restart), it fires immediately.
func WatchExecDeadline(executionID string, execDate time.Time, selfID *peer.Peer, request *tools.APIRequest) {
func WatchExecDeadline(executionID string, ns string, execDate time.Time, selfID *peer.Peer, request *tools.APIRequest) {
fmt.Println("WatchExecDeadline")
delay := time.Until(execDate.UTC().Add(-1 * time.Minute))
if delay <= 0 {
go purgeUnconfirmedExecution(executionID, selfID, request)
go handleExecDeadline(executionID, ns, selfID, request)
return
}
time.AfterFunc(delay, func() { purgeUnconfirmedExecution(executionID, selfID, request) })
time.AfterFunc(delay, func() { handleExecDeadline(executionID, ns, selfID, request) })
}
func purgeUnconfirmedExecution(executionID string, selfID *peer.Peer, request *tools.APIRequest) {
acc := workflow_execution.NewAccessor(&tools.APIRequest{Admin: true})
UnscheduleExecution(executionID, selfID, request)
_, _, err := acc.DeleteOne(executionID)
fmt.Printf("purgeUnconfirmedExecution: cleaned up resources for execution %s\n", err)
func handleExecDeadline(executionID string, ns string, selfID *peer.Peer, request *tools.APIRequest) {
adminReq := &tools.APIRequest{Admin: true}
res, _, err := workflow_execution.NewAccessor(adminReq).LoadOne(executionID)
if err != nil || res == nil {
fmt.Printf("handleExecDeadline: execution %s not found\n", executionID)
return
}
exec := res.(*workflow_execution.WorkflowExecution)
if exec.IsDraft {
UnscheduleExecution(executionID, selfID, request)
workflow_execution.NewAccessor(adminReq).DeleteOne(executionID)
fmt.Printf("handleExecDeadline: purged draft execution %s\n", executionID)
return
}
if serv, err := tools.NewKubernetesService(
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData); err != nil {
fmt.Printf("handleExecDeadline: k8s init failed for %s: %v\n", executionID, err)
} else if err := serv.ProvisionExecutionNamespace(context.Background(), ns); err != nil {
fmt.Printf("handleExecDeadline: failed to provision namespace for %s: %v\n", ns, err)
}
go WatchExecEnd(executionID, ns, exec.EndDate, exec.ExecDate)
}
// WatchExecEnd fires at the execution end date (ExecDate+1h when EndDate is nil)
// and deletes the Kubernetes namespace associated with the execution.
func WatchExecEnd(executionID string, ns string, endDate *time.Time, execDate time.Time) {
var end time.Time
if endDate != nil {
end = *endDate
} else {
end = execDate.UTC().Add(time.Hour)
}
delay := time.Until(end.UTC())
fire := func() {
serv, err := tools.NewKubernetesService(
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
if err != nil {
fmt.Printf("WatchExecEnd: k8s init failed for %s: %v\n", executionID, err)
return
}
if err := serv.TeardownExecutionNamespace(context.Background(), ns); err != nil {
fmt.Printf("WatchExecEnd: failed to teardown namespace %s: %v\n", ns, err)
}
}
if delay <= 0 {
go fire()
return
}
time.AfterFunc(delay, fire)
}
// RecoverDraftExecutions is called at startup to restore deadline watchers for
@@ -308,7 +358,7 @@ func RecoverDraftExecutions() {
continue
}
RegisterExecLock(exec.GetID())
go WatchExecDeadline(exec.GetID(), exec.ExecDate, selfID, adminReq)
go WatchExecDeadline(exec.GetID(), exec.ExecutionsID, exec.ExecDate, selfID, adminReq)
}
fmt.Printf("RecoverDraftExecutions: recovered %d draft executions\n", len(results))
}