oc-scheduler -> scheduling + logs
This commit is contained in:
@@ -2,6 +2,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
@@ -23,6 +24,8 @@ var BookingExample booking.Booking
|
|||||||
// @Description search bookings by execution
|
// @Description search bookings by execution
|
||||||
// @Param id path string true "id execution"
|
// @Param id path string true "id execution"
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {workspace} models.workspace
|
// @Success 200 {workspace} models.workspace
|
||||||
// @router /search/execution/:id [get]
|
// @router /search/execution/:id [get]
|
||||||
func (o *BookingController) ExecutionSearch() {
|
func (o *BookingController) ExecutionSearch() {
|
||||||
@@ -39,12 +42,14 @@ func (o *BookingController) ExecutionSearch() {
|
|||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
id := o.Ctx.Input.Param(":id")
|
id := o.Ctx.Input.Param(":id")
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
f := dbs.Filters{
|
f := dbs.Filters{
|
||||||
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
|
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
|
||||||
"execution_id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
"execution_id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true", int64(offset), int64(limit))
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +58,8 @@ func (o *BookingController) ExecutionSearch() {
|
|||||||
// @Param start_date path string true "the word search you want to get"
|
// @Param start_date path string true "the word search you want to get"
|
||||||
// @Param end_date path string true "the word search you want to get"
|
// @Param end_date path string true "the word search you want to get"
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {workspace} models.workspace
|
// @Success 200 {workspace} models.workspace
|
||||||
// @router /search/:start_date/:end_date [get]
|
// @router /search/:start_date/:end_date [get]
|
||||||
func (o *BookingController) Search() {
|
func (o *BookingController) Search() {
|
||||||
@@ -67,6 +74,8 @@ func (o *BookingController) Search() {
|
|||||||
*/
|
*/
|
||||||
// store and return Id or post with UUID
|
// store and return Id or post with UUID
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
start_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":start_date"), time.UTC)
|
start_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":start_date"), time.UTC)
|
||||||
end_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":end_date"), time.UTC)
|
end_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":end_date"), time.UTC)
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
@@ -77,19 +86,23 @@ func (o *BookingController) Search() {
|
|||||||
"execution_date": {{Operator: "gte", Value: sd}, {Operator: "lte", Value: ed}},
|
"execution_date": {{Operator: "gte", Value: sd}, {Operator: "lte", Value: ed}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true", int64(offset), int64(limit))
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Title GetAll
|
// @Title GetAll
|
||||||
// @Description find booking by id
|
// @Description find booking by id
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {booking} models.booking
|
// @Success 200 {booking} models.booking
|
||||||
// @router / [get]
|
// @router / [get]
|
||||||
func (o *BookingController) GetAll() {
|
func (o *BookingController) GetAll() {
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true")
|
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit))
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"oc-scheduler/infrastructure"
|
"oc-scheduler/infrastructure"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
"cloud.o-forge.io/core/oc-lib/models/execution_verification"
|
"cloud.o-forge.io/core/oc-lib/models/execution_verification"
|
||||||
@@ -19,12 +20,16 @@ type ExecutionVerificationController struct {
|
|||||||
// @Title GetAll
|
// @Title GetAll
|
||||||
// @Description find verification by id
|
// @Description find verification by id
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {booking} models.booking
|
// @Success 200 {booking} models.booking
|
||||||
// @router / [get]
|
// @router / [get]
|
||||||
func (o *ExecutionVerificationController) GetAll() {
|
func (o *ExecutionVerificationController) GetAll() {
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.EXECUTION_VERIFICATION), user, peerID, groups, nil).LoadAll(isDraft == "true")
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
|
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.EXECUTION_VERIFICATION), user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit))
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"cloud.o-forge.io/core/oc-lib/config"
|
"cloud.o-forge.io/core/oc-lib/config"
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
gorillaws "github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operations about workflow
|
// Operations about workflow
|
||||||
@@ -84,3 +86,91 @@ func (o *LokiController) GetLogs() {
|
|||||||
o.Data["json"] = map[string]string{"error": "Query error"}
|
o.Data["json"] = map[string]string{"error": "Query error"}
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogsStreamHandler streams Loki logs over WebSocket.
|
||||||
|
//
|
||||||
|
// The client sends one JSON message with the same format as GetLogs:
|
||||||
|
//
|
||||||
|
// {"start": "<unix-seconds>", "label1": "val1", ...}
|
||||||
|
//
|
||||||
|
// The server connects to Loki's /loki/api/v1/tail WebSocket endpoint and
|
||||||
|
// forwards every message it receives until the client disconnects.
|
||||||
|
func LogsStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := wsUpgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
var query map[string]interface{}
|
||||||
|
if err := conn.ReadJSON(&query); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
start := fmt.Sprintf("%v", query["start"])
|
||||||
|
if len(start) > 10 {
|
||||||
|
start = start[:10]
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels []string
|
||||||
|
for k, v := range query {
|
||||||
|
if k == "start" || k == "end" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labels = append(labels, fmt.Sprintf("%v=\"%v\"", k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) == 0 || len(start) < 10 {
|
||||||
|
_ = conn.WriteJSON(map[string]string{"error": "missing start or query labels"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Loki tail WS URL (http→ws, https→wss).
|
||||||
|
lokiBase := config.GetConfig().LokiUrl
|
||||||
|
lokiBase = strings.Replace(lokiBase, "https://", "wss://", 1)
|
||||||
|
lokiBase = strings.Replace(lokiBase, "http://", "ws://", 1)
|
||||||
|
|
||||||
|
lokiURL := lokiBase + "/loki/api/v1/tail?" + url.Values{
|
||||||
|
"query": {"{" + strings.Join(labels, ", ") + "}"},
|
||||||
|
"start": {start + "000000000"}, // seconds → nanoseconds
|
||||||
|
}.Encode()
|
||||||
|
|
||||||
|
lokiConn, _, err := gorillaws.DefaultDialer.Dial(lokiURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.WriteJSON(map[string]string{"error": "loki: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lokiConn.Close()
|
||||||
|
|
||||||
|
errCh := make(chan error, 2)
|
||||||
|
|
||||||
|
// Forward Loki → client.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, msg, err := lokiConn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result map[string]interface{}
|
||||||
|
if json.Unmarshal(msg, &result) == nil {
|
||||||
|
if err := conn.WriteJSON(result); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Detect client disconnect (read pump).
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if _, _, err := conn.ReadMessage(); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-errCh
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"oc-scheduler/infrastructure"
|
"oc-scheduler/infrastructure"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||||
@@ -27,12 +31,66 @@ var wsUpgrader = gorillaws.Upgrader{
|
|||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var schedulerMu sync.RWMutex
|
||||||
|
var scheduler = map[string]*infrastructure.WorkflowSchedule{}
|
||||||
|
|
||||||
|
func realPushCheckfunc(ctx context.Context, conn *gorillaws.Conn, req *tools.APIRequest, user string, ws infrastructure.WorkflowSchedule,
|
||||||
|
executionsID string, wfID string, scheduled bool, asap bool, preemption bool, reschedule bool) (bool, error) {
|
||||||
|
// If we already have draft bookings for this session and we're about to
|
||||||
|
// re-check (timer refresh or planner update), remove the old drafts first
|
||||||
|
// so the planner doesn't treat our own previous reservations as conflicts.
|
||||||
|
if reschedule && scheduled {
|
||||||
|
infrastructure.CleanupSession(executionsID, req)
|
||||||
|
scheduled = false
|
||||||
|
}
|
||||||
|
workflowScheduler := ws
|
||||||
|
|
||||||
|
schedulerMu.Lock()
|
||||||
|
if scheduler[user] != nil {
|
||||||
|
workflowScheduler = *scheduler[user]
|
||||||
|
}
|
||||||
|
schedulerMu.Unlock()
|
||||||
|
result, checkErr := workflowScheduler.Check(wfID, asap, preemption, req)
|
||||||
|
fmt.Println("CHECK", checkErr)
|
||||||
|
if checkErr != nil {
|
||||||
|
return scheduled, checkErr
|
||||||
|
}
|
||||||
|
if result.Available && reschedule {
|
||||||
|
workflowScheduler.Start = result.Start
|
||||||
|
if result.End != nil {
|
||||||
|
workflowScheduler.End = result.End
|
||||||
|
}
|
||||||
|
_, _, execs, purchases, bookings, err := workflowScheduler.GetBuyAndBook(wfID, req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("GetBuyAndBook", err)
|
||||||
|
return scheduled, err
|
||||||
|
}
|
||||||
|
infrastructure.UpsertSessionDrafts(executionsID, execs, purchases, bookings, req)
|
||||||
|
scheduled = true
|
||||||
|
delay := workflowScheduler.Start.UTC().Add(-(1 * time.Minute)).Sub(time.Now().UTC())
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Session closed before timer fired — nothing to do, CleanupSession
|
||||||
|
// has already run (or will run) in the defer of CheckStreamHandler.
|
||||||
|
return
|
||||||
|
case <-time.After(delay):
|
||||||
|
realPushCheckfunc(ctx, conn, req, user, ws, executionsID, wfID, scheduled, asap, preemption, true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
result.SchedulingID = executionsID
|
||||||
|
fmt.Println(result)
|
||||||
|
return scheduled, conn.WriteJSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
// CheckStreamHandler is the WebSocket handler for slot availability checking.
|
// CheckStreamHandler is the WebSocket handler for slot availability checking.
|
||||||
// Query params: as_possible=true, preemption=true
|
// Query params: as_possible=true, preemption=true
|
||||||
func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
wfID := strings.TrimSuffix(
|
wfID := strings.TrimSuffix(
|
||||||
strings.TrimPrefix(r.URL.Path, "/oc/"),
|
strings.TrimPrefix(r.URL.Path, "/oc/check/"),
|
||||||
"/check",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
@@ -49,22 +107,31 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchedPeers, err := infrastructure.GetWorkflowPeerIDs(wfID, req)
|
watchedPeers, err := infrastructure.GetWorkflowPeerIDs(wfID, req)
|
||||||
fmt.Println("Watched peers for workflow", wfID, ":", watchedPeers)
|
fmt.Println("Watched peers for workflow", wfID, ":", watchedPeers, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, `{"code":404,"error":"`+err.Error()+`"}`, http.StatusNotFound)
|
http.Error(w, `{"code":404,"error":"`+err.Error()+`"}`, http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := wsUpgrader.Upgrade(w, r, nil)
|
conn, err := wsUpgrader.Upgrade(w, r, nil)
|
||||||
|
fmt.Println("Upgrade :", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ws infrastructure.WorkflowSchedule
|
var ws infrastructure.WorkflowSchedule
|
||||||
if err := conn.ReadJSON(&ws); err != nil {
|
if err := conn.ReadJSON(&ws); err != nil {
|
||||||
|
fmt.Println("ReadJSON :", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Allow the initial JSON to override the query-param mode.
|
||||||
|
if ws.Asap != nil {
|
||||||
|
asap = *ws.Asap
|
||||||
|
}
|
||||||
|
if ws.Preemption != nil {
|
||||||
|
preemption = *ws.Preemption
|
||||||
|
}
|
||||||
|
|
||||||
plannerCh, plannerUnsub := infrastructure.SubscribePlannerUpdates(watchedPeers)
|
plannerCh, plannerUnsub := infrastructure.SubscribePlannerUpdates(watchedPeers)
|
||||||
wfCh, wfUnsub := infrastructure.SubscribeWorkflowUpdates(wfID)
|
wfCh, wfUnsub := infrastructure.SubscribeWorkflowUpdates(wfID)
|
||||||
@@ -83,7 +150,9 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
scheduled := false
|
scheduled := false
|
||||||
confirmed := false
|
confirmed := false
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer func() {
|
defer func() {
|
||||||
|
cancel()
|
||||||
conn.Close()
|
conn.Close()
|
||||||
plannerUnsub()
|
plannerUnsub()
|
||||||
wfUnsub()
|
wfUnsub()
|
||||||
@@ -93,28 +162,9 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
pushCheck := func(reschedule bool) error {
|
pushCheck := realPushCheckfunc
|
||||||
result, checkErr := ws.Check(wfID, asap, preemption, req)
|
if scheduled, err = pushCheck(ctx, conn, req, user, ws, executionsID, wfID, scheduled, asap, preemption, true); err != nil {
|
||||||
if checkErr != nil {
|
fmt.Println("UPDATE CONFIRM FIRST scheduled", err)
|
||||||
return checkErr
|
|
||||||
}
|
|
||||||
if result.Available && reschedule {
|
|
||||||
ws.Start = result.Start
|
|
||||||
if result.End != nil {
|
|
||||||
ws.End = result.End
|
|
||||||
}
|
|
||||||
_, _, execs, purchases, bookings, err := ws.GetBuyAndBook(wfID, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
infrastructure.UpsertSessionDrafts(executionsID, execs, purchases, bookings, req)
|
|
||||||
scheduled = true
|
|
||||||
}
|
|
||||||
result.SchedulingID = executionsID
|
|
||||||
return conn.WriteJSON(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pushCheck(true); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +189,17 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case updated := <-updateCh:
|
case updated := <-updateCh:
|
||||||
|
fmt.Println("updated FOUND ", updated)
|
||||||
|
workflowScheduler := ws
|
||||||
|
schedulerMu.Lock()
|
||||||
|
if scheduler[user] != nil {
|
||||||
|
workflowScheduler = *scheduler[user]
|
||||||
|
}
|
||||||
|
schedulerMu.Unlock()
|
||||||
|
|
||||||
if updated.Confirm {
|
if updated.Confirm {
|
||||||
ws.UUID = executionsID
|
workflowScheduler.UUID = executionsID
|
||||||
_, _, _, schedErr := infrastructure.Schedule(&ws, wfID, req)
|
_, _, _, schedErr := infrastructure.Schedule(&workflowScheduler, wfID, req)
|
||||||
if schedErr != nil {
|
if schedErr != nil {
|
||||||
_ = conn.WriteJSON(map[string]interface{}{
|
_ = conn.WriteJSON(map[string]interface{}{
|
||||||
"error": schedErr.Error(),
|
"error": schedErr.Error(),
|
||||||
@@ -149,38 +207,62 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
confirmed = true
|
confirmed = true
|
||||||
|
fmt.Println("UPDATE CONFIRM")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
changed := updated.Cron != ws.Cron ||
|
// Detect mode change before updating local vars.
|
||||||
!updated.Start.Equal(ws.Start) ||
|
modeChanged := (updated.Asap != nil && *updated.Asap != asap) ||
|
||||||
updated.DurationS != ws.DurationS ||
|
(updated.Preemption != nil && *updated.Preemption != preemption)
|
||||||
(updated.End == nil) != (ws.End == nil) ||
|
if updated.Asap != nil {
|
||||||
(updated.End != nil && ws.End != nil && !updated.End.Equal(*ws.End)) ||
|
asap = *updated.Asap
|
||||||
updated.BookingMode != ws.BookingMode ||
|
}
|
||||||
!reflect.DeepEqual(updated.SelectedBillingStrategy, ws.SelectedBillingStrategy) ||
|
if updated.Preemption != nil {
|
||||||
!reflect.DeepEqual(updated.SelectedInstances, ws.SelectedInstances) ||
|
preemption = *updated.Preemption
|
||||||
!reflect.DeepEqual(updated.SelectedPartnerships, ws.SelectedPartnerships) ||
|
}
|
||||||
!reflect.DeepEqual(updated.SelectedBuyings, ws.SelectedBuyings) ||
|
changed := modeChanged ||
|
||||||
!reflect.DeepEqual(updated.SelectedStrategies, ws.SelectedStrategies)
|
updated.Cron != workflowScheduler.Cron ||
|
||||||
|
!updated.Start.Equal(workflowScheduler.Start) ||
|
||||||
|
updated.DurationS != workflowScheduler.DurationS ||
|
||||||
|
(updated.End == nil) != (workflowScheduler.End == nil) ||
|
||||||
|
(updated.End != nil && workflowScheduler.End != nil && !updated.End.Equal(*workflowScheduler.End)) ||
|
||||||
|
updated.BookingMode != workflowScheduler.BookingMode ||
|
||||||
|
!reflect.DeepEqual(updated.SelectedBillingStrategy, workflowScheduler.SelectedBillingStrategy) ||
|
||||||
|
!reflect.DeepEqual(updated.SelectedInstances, workflowScheduler.SelectedInstances) ||
|
||||||
|
!reflect.DeepEqual(updated.SelectedPartnerships, workflowScheduler.SelectedPartnerships) ||
|
||||||
|
!reflect.DeepEqual(updated.SelectedBuyings, workflowScheduler.SelectedBuyings) ||
|
||||||
|
!reflect.DeepEqual(updated.SelectedStrategies, workflowScheduler.SelectedStrategies)
|
||||||
|
|
||||||
infrastructure.CleanupSession(executionsID, req)
|
infrastructure.CleanupSession(executionsID, req)
|
||||||
ws = updated
|
|
||||||
if err := pushCheck(changed || !scheduled); err != nil {
|
schedulerMu.Lock()
|
||||||
|
scheduler[user] = &updated
|
||||||
|
schedulerMu.Unlock()
|
||||||
|
|
||||||
|
if scheduled, err = pushCheck(ctx, conn, req, user, ws, executionsID, wfID, scheduled, asap, preemption, changed || !scheduled); err != nil {
|
||||||
|
fmt.Println("UPDATE SCHEDULERD", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case remotePeerID := <-plannerCh:
|
case remotePeerID := <-plannerCh:
|
||||||
|
workflowScheduler := ws
|
||||||
|
schedulerMu.Lock()
|
||||||
|
if scheduler[user] != nil {
|
||||||
|
workflowScheduler = *scheduler[user]
|
||||||
|
}
|
||||||
|
schedulerMu.Unlock()
|
||||||
if remotePeerID == selfPeerID {
|
if remotePeerID == selfPeerID {
|
||||||
if scheduled {
|
if scheduled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result, checkErr := ws.Check(wfID, asap, preemption, req)
|
result, checkErr := workflowScheduler.Check(wfID, asap, preemption, req)
|
||||||
if checkErr == nil {
|
if checkErr == nil {
|
||||||
result.SchedulingID = executionsID
|
result.SchedulingID = executionsID
|
||||||
_ = conn.WriteJSON(result)
|
_ = conn.WriteJSON(result)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := pushCheck(scheduled); err != nil {
|
if scheduled, err = pushCheck(ctx, conn, req, user, ws, executionsID, wfID, scheduled, asap, preemption, scheduled); err != nil {
|
||||||
|
fmt.Println("UPDATE SCHEDULERD PLAN", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,11 +274,13 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
newOwned := infrastructure.RequestPlannerRefresh(newPeers, executionsID)
|
newOwned := infrastructure.RequestPlannerRefresh(newPeers, executionsID)
|
||||||
ownedPeers = append(ownedPeers, newOwned...)
|
ownedPeers = append(ownedPeers, newOwned...)
|
||||||
}
|
}
|
||||||
if err := pushCheck(false); err != nil {
|
if scheduled, err = pushCheck(ctx, conn, req, user, ws, executionsID, wfID, scheduled, asap, preemption, false); err != nil {
|
||||||
|
fmt.Println("UPDATE WORKFLOW", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-closeCh:
|
case <-closeCh:
|
||||||
|
fmt.Println("UPDATE Close ? ")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,9 +311,13 @@ func (o *WorkflowSchedulerController) UnSchedule() {
|
|||||||
// @Title SearchScheduledDraftOrder
|
// @Title SearchScheduledDraftOrder
|
||||||
// @Description search draft order for a workflow
|
// @Description search draft order for a workflow
|
||||||
// @Param id path string true "id execution"
|
// @Param id path string true "id execution"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {workspace} models.workspace
|
// @Success 200 {workspace} models.workspace
|
||||||
// @router /:id/order [get]
|
// @router /order/:id [get]
|
||||||
func (o *WorkflowSchedulerController) SearchScheduledDraftOrder() {
|
func (o *WorkflowSchedulerController) SearchScheduledDraftOrder() {
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
_, peerID, _ := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
_, peerID, _ := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
id := o.Ctx.Input.Param(":id")
|
id := o.Ctx.Input.Param(":id")
|
||||||
filter := &dbs.Filters{
|
filter := &dbs.Filters{
|
||||||
@@ -238,6 +326,6 @@ func (o *WorkflowSchedulerController) SearchScheduledDraftOrder() {
|
|||||||
"order_by": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
"order_by": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
o.Data["json"] = oclib.NewRequestAdmin(orderCollection, nil).Search(filter, "", true)
|
o.Data["json"] = oclib.NewRequestAdmin(orderCollection, nil).Search(filter, "", true, int64(offset), int64(limit))
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
@@ -21,6 +22,8 @@ type WorkflowExecutionController struct {
|
|||||||
// @Param start_date path string true "the word search you want to get"
|
// @Param start_date path string true "the word search you want to get"
|
||||||
// @Param end_date path string true "the word search you want to get"
|
// @Param end_date path string true "the word search you want to get"
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {workspace} models.workspace
|
// @Success 200 {workspace} models.workspace
|
||||||
// @router /search/:start_date/:end_date [get]
|
// @router /search/:start_date/:end_date [get]
|
||||||
func (o *WorkflowExecutionController) SearchPerDate() {
|
func (o *WorkflowExecutionController) SearchPerDate() {
|
||||||
@@ -35,6 +38,8 @@ func (o *WorkflowExecutionController) SearchPerDate() {
|
|||||||
*/
|
*/
|
||||||
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
// store and return Id or post with UUID
|
// store and return Id or post with UUID
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
start_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":start_date"), time.UTC)
|
start_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":start_date"), time.UTC)
|
||||||
end_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":end_date"), time.UTC)
|
end_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":end_date"), time.UTC)
|
||||||
sd := primitive.NewDateTimeFromTime(start_date)
|
sd := primitive.NewDateTimeFromTime(start_date)
|
||||||
@@ -46,7 +51,7 @@ func (o *WorkflowExecutionController) SearchPerDate() {
|
|||||||
}
|
}
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
// o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
// o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
||||||
o.Data["json"] = oclib.NewRequestAdmin(collection, nil).Search(&f, "", isDraft == "true")
|
o.Data["json"] = oclib.NewRequestAdmin(collection, nil).Search(&f, "", isDraft == "true", int64(offset), int64(limit))
|
||||||
|
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -54,13 +59,16 @@ func (o *WorkflowExecutionController) SearchPerDate() {
|
|||||||
// @Title GetAll
|
// @Title GetAll
|
||||||
// @Description find workflow by workflowid
|
// @Description find workflow by workflowid
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {workflow} models.workflow
|
// @Success 200 {workflow} models.workflow
|
||||||
// @router / [get]
|
// @router / [get]
|
||||||
func (o *WorkflowExecutionController) GetAll() {
|
func (o *WorkflowExecutionController) GetAll() {
|
||||||
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
// o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).LoadAll(isDraft == "true")
|
o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit))
|
||||||
o.Data["json"] = oclib.NewRequestAdmin(collection, nil).LoadAll(isDraft == "true")
|
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +89,16 @@ func (o *WorkflowExecutionController) Get() {
|
|||||||
// @Description find compute by key word
|
// @Description find compute by key word
|
||||||
// @Param search path string true "the search you want to get"
|
// @Param search path string true "the search you want to get"
|
||||||
// @Param is_draft query string false "draft wished"
|
// @Param is_draft query string false "draft wished"
|
||||||
|
// @Param offset query string false
|
||||||
|
// @Param limit query string false
|
||||||
// @Success 200 {compute} models.compute
|
// @Success 200 {compute} models.compute
|
||||||
// @router /search/:search [get]
|
// @router /search/:search [get]
|
||||||
func (o *WorkflowExecutionController) Search() {
|
func (o *WorkflowExecutionController) Search() {
|
||||||
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
|
offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset"))
|
||||||
|
limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit"))
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
search := o.Ctx.Input.Param(":search")
|
search := o.Ctx.Input.Param(":search")
|
||||||
// o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).Search(nil, search, isDraft == "true")
|
o.Data["json"] = oclib.NewRequest(collection, user, peerID, groups, nil).Search(nil, search, isDraft == "true", int64(offset), int64(limit))
|
||||||
o.Data["json"] = oclib.NewRequestAdmin(collection, nil).Search(nil, search, isDraft == "true")
|
|
||||||
|
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module oc-scheduler
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260407090927-6fe91eda875d
|
||||||
github.com/beego/beego/v2 v2.3.8
|
github.com/beego/beego/v2 v2.3.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/robfig/cron v1.2.0
|
github.com/robfig/cron v1.2.0
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,5 +1,15 @@
|
|||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b h1:y0rppyzGIQTIyvapWwHZ8t20wMaSaMU6NoZLkMCui8w=
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b h1:y0rppyzGIQTIyvapWwHZ8t20wMaSaMU6NoZLkMCui8w=
|
||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260326110203-87cf2cb12af0 h1:pQf9k+GSzNGEmrUa00jn9Zcqfp9X4N1Z5ie7InvUf3g=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260326110203-87cf2cb12af0/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260330082109-a4ab3285e34c h1:M0y5jI9BO7fyi1nMa2S2hhY0jDbBC+Bg56+5tp9g/vs=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260330082109-a4ab3285e34c/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260403121807-913d9b3dfb0a h1:H7K91js08Vyx307MW6BwQ/kqNGTrQVMaR3xvrIrc2W8=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260403121807-913d9b3dfb0a/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260407090336-526eaef33aa1 h1:uq6ZAHAqKVF9X45JnBA6+6Nu/nUwOgN8ezWWDz+bzaw=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260407090336-526eaef33aa1/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260407090927-6fe91eda875d h1:54Vl14gurwAkmZEaWZKUM5eDZfB7MF/fzWjibWLQljE=
|
||||||
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260407090927-6fe91eda875d/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
|
|||||||
@@ -99,9 +99,11 @@ func UpdateExecutionState(payload []byte, dt tools.DataType) {
|
|||||||
switch dt {
|
switch dt {
|
||||||
case tools.BOOKING:
|
case tools.BOOKING:
|
||||||
if exec.BookingsState == nil {
|
if exec.BookingsState == nil {
|
||||||
exec.BookingsState = map[string]bool{}
|
exec.BookingsState = map[string]workflow_execution.BookingState{}
|
||||||
}
|
}
|
||||||
exec.BookingsState[data.ID] = true
|
st := exec.BookingsState[data.ID]
|
||||||
|
st.IsBooked = true
|
||||||
|
exec.BookingsState[data.ID] = st
|
||||||
case tools.PURCHASE_RESOURCE:
|
case tools.PURCHASE_RESOURCE:
|
||||||
if exec.PurchasesState == nil {
|
if exec.PurchasesState == nil {
|
||||||
exec.PurchasesState = map[string]bool{}
|
exec.PurchasesState = map[string]bool{}
|
||||||
@@ -111,7 +113,7 @@ func UpdateExecutionState(payload []byte, dt tools.DataType) {
|
|||||||
|
|
||||||
allConfirmed := true
|
allConfirmed := true
|
||||||
for _, st := range exec.BookingsState {
|
for _, st := range exec.BookingsState {
|
||||||
if !st {
|
if !st.IsBooked {
|
||||||
allConfirmed = false
|
allConfirmed = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -145,7 +147,7 @@ func confirmSessionOrder(executionsID string, adminReq *tools.APIRequest) {
|
|||||||
results, _, _ := order.NewAccessor(adminReq).Search(
|
results, _, _ := order.NewAccessor(adminReq).Search(
|
||||||
&dbs.Filters{And: map[string][]dbs.Filter{
|
&dbs.Filters{And: map[string][]dbs.Filter{
|
||||||
"executions_id": {{Operator: dbs.EQUAL.String(), Value: executionsID}},
|
"executions_id": {{Operator: dbs.EQUAL.String(), Value: executionsID}},
|
||||||
}}, "", true)
|
}}, "", true, 0, 10000)
|
||||||
for _, obj := range results {
|
for _, obj := range results {
|
||||||
if o, ok := obj.(*order.Order); ok {
|
if o, ok := obj.(*order.Order); ok {
|
||||||
o.IsDraft = false
|
o.IsDraft = false
|
||||||
@@ -301,7 +303,7 @@ func Unschedule(executionID string, request *tools.APIRequest) error {
|
|||||||
|
|
||||||
func RecoverDraft() {
|
func RecoverDraft() {
|
||||||
adminReq := &tools.APIRequest{Admin: true}
|
adminReq := &tools.APIRequest{Admin: true}
|
||||||
results, _, _ := workflow_execution.NewAccessor(adminReq).Search(nil, "*", true)
|
results, _, _ := workflow_execution.NewAccessor(adminReq).Search(nil, "*", true, 0, 10000)
|
||||||
for _, obj := range results {
|
for _, obj := range results {
|
||||||
exec, ok := obj.(*workflow_execution.WorkflowExecution)
|
exec, ok := obj.(*workflow_execution.WorkflowExecution)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -350,6 +352,13 @@ func HandleWorkflowDone(resp tools.NATSResponse) {
|
|||||||
if evt.RealEnd != nil {
|
if evt.RealEnd != nil {
|
||||||
exec.EndDate = evt.RealEnd
|
exec.EndDate = evt.RealEnd
|
||||||
}
|
}
|
||||||
|
// All bookings are no longer reserved and are done
|
||||||
|
if exec.BookingsState == nil {
|
||||||
|
exec.BookingsState = map[string]workflow_execution.BookingState{}
|
||||||
|
}
|
||||||
|
for id := range exec.BookingsState {
|
||||||
|
exec.BookingsState[id] = workflow_execution.BookingState{IsBooked: false, IsDone: true}
|
||||||
|
}
|
||||||
utils.GenericRawUpdateOne(exec, exec.GetID(), workflow_execution.NewAccessor(adminReq))
|
utils.GenericRawUpdateOne(exec, exec.GetID(), workflow_execution.NewAccessor(adminReq))
|
||||||
for _, step := range evt.Steps {
|
for _, step := range evt.Steps {
|
||||||
applyStepToBooking(step, adminReq)
|
applyStepToBooking(step, adminReq)
|
||||||
@@ -379,6 +388,21 @@ func HandleWorkflowStepDone(resp tools.NATSResponse) {
|
|||||||
bk.RealEndDate = evt.RealEnd
|
bk.RealEndDate = evt.RealEnd
|
||||||
}
|
}
|
||||||
utils.GenericRawUpdateOne(bk, bk.GetID(), booking.NewAccessor(adminReq))
|
utils.GenericRawUpdateOne(bk, bk.GetID(), booking.NewAccessor(adminReq))
|
||||||
|
|
||||||
|
// Update BookingsState in the parent WorkflowExecution: resource released, step done
|
||||||
|
execRes, _, execErr := workflow_execution.NewAccessor(adminReq).LoadOne(bk.ExecutionID)
|
||||||
|
if execErr == nil && execRes != nil {
|
||||||
|
exec := execRes.(*workflow_execution.WorkflowExecution)
|
||||||
|
if exec.BookingsState == nil {
|
||||||
|
exec.BookingsState = map[string]workflow_execution.BookingState{}
|
||||||
|
}
|
||||||
|
st := exec.BookingsState[evt.BookingID]
|
||||||
|
st.IsBooked = false
|
||||||
|
st.IsDone = true
|
||||||
|
exec.BookingsState[evt.BookingID] = st
|
||||||
|
utils.GenericRawUpdateOne(exec, exec.GetID(), workflow_execution.NewAccessor(adminReq))
|
||||||
|
}
|
||||||
|
|
||||||
switch bk.State {
|
switch bk.State {
|
||||||
case enum.SUCCESS, enum.FAILURE, enum.FORGOTTEN, enum.CANCELLED:
|
case enum.SUCCESS, enum.FAILURE, enum.FORGOTTEN, enum.CANCELLED:
|
||||||
self, err := oclib.GetMySelf()
|
self, err := oclib.GetMySelf()
|
||||||
@@ -439,7 +463,7 @@ func scanStaleExecutions() error {
|
|||||||
res := oclib.NewRequest(oclib.LibDataEnum(oclib.WORKFLOW_EXECUTION), "", myself.GetID(), []string{}, nil).
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.WORKFLOW_EXECUTION), "", myself.GetID(), []string{}, nil).
|
||||||
Search(&dbs.Filters{And: map[string][]dbs.Filter{
|
Search(&dbs.Filters{And: map[string][]dbs.Filter{
|
||||||
"execution_date": {{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(deadline)}},
|
"execution_date": {{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(deadline)}},
|
||||||
}}, "", false)
|
}}, "", false, 0, 10000)
|
||||||
if res.Err != "" {
|
if res.Err != "" {
|
||||||
return fmt.Errorf("stale execution search failed: %s", res.Err)
|
return fmt.Errorf("stale execution search failed: %s", res.Err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ func (s *PlannerService) HandleStore(resp tools.NATSResponse) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(resp.Payload, &p); err != nil {
|
if err := json.Unmarshal(resp.Payload, &p); err != nil {
|
||||||
|
fmt.Println("RETRIEVE PLANNER ERR", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.Store(fmt.Sprintf("%v", m["peer_id"]), &p)
|
s.Store(fmt.Sprintf("%v", m["peer_id"]), &p)
|
||||||
@@ -128,11 +129,12 @@ func (s *PlannerService) FindDate(wfID string, checkables map[string]utils.Booki
|
|||||||
if asap {
|
if asap {
|
||||||
next := s.findNextSlot(checkables, start, end, checkWindowHours)
|
next := s.findNextSlot(checkables, start, end, checkWindowHours)
|
||||||
if next != nil {
|
if next != nil {
|
||||||
start = *next
|
|
||||||
if end != nil {
|
if end != nil {
|
||||||
shifted := next.Add(end.Sub(start))
|
duration := end.Sub(start) // capture before overwriting start
|
||||||
end = &shifted
|
e := next.Add(duration)
|
||||||
|
end = &e
|
||||||
}
|
}
|
||||||
|
start = *next
|
||||||
return start, end, true, false, warnings
|
return start, end, true, false, warnings
|
||||||
} else {
|
} else {
|
||||||
return start, end, false, false, warnings
|
return start, end, false, false, warnings
|
||||||
@@ -142,20 +144,84 @@ func (s *PlannerService) FindDate(wfID string, checkables map[string]utils.Booki
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlannerService) Fill(checkables map[string]utils.BookingResource, wfID string) {
|
func (s *PlannerService) Fill(checkables map[string]utils.BookingResource, wfID string) {
|
||||||
if missing := s.MissingPeers(checkables); len(missing) > 0 {
|
// Collect all peers involved in this check (not just missing ones).
|
||||||
const plannerFetchTimeout = 2 * time.Second
|
// We always re-request every peer because PB_CLOSE_PLANNER is emitted
|
||||||
tmpSession := "check-oneshot-" + wfID
|
// after each check session, which stops the remote stream. The cached
|
||||||
ch, cancelSub := SubscribeUpdates(s.Subs, &s.SubMu, missing...)
|
// snapshot may therefore be stale: re-fetching ensures the check is made
|
||||||
owned := s.Refresh(missing, tmpSession)
|
// against up-to-date availability data.
|
||||||
|
all := s.allPeers(checkables)
|
||||||
|
if len(all) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const plannerFetchTimeout = 5 * time.Second
|
||||||
|
tmpSession := "check-oneshot-" + wfID
|
||||||
|
|
||||||
|
// Mark pending entries and clear any stale planner so the wait loop below
|
||||||
|
// will not return early with an old snapshot.
|
||||||
|
s.Mu.Lock()
|
||||||
|
myself, _ := oclib.GetMySelf()
|
||||||
|
for _, peerID := range all {
|
||||||
|
entry := s.Cache[peerID]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &plannerEntry{}
|
||||||
|
s.Cache[peerID] = entry
|
||||||
|
s.AddedAt[peerID] = time.Now().UTC()
|
||||||
|
go s.EvictAfter(peerID, plannerTTL)
|
||||||
|
}
|
||||||
|
// Reset so MissingPeers sees it as absent until the fresh snapshot arrives.
|
||||||
|
entry.Planner = nil
|
||||||
|
if !entry.Refreshing {
|
||||||
|
entry.Refreshing = true
|
||||||
|
entry.RefreshOwner = tmpSession
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Mu.Unlock()
|
||||||
|
defer s.ReleaseRefreshOwnership(all, tmpSession)
|
||||||
|
|
||||||
|
for _, peerID := range all {
|
||||||
|
if myself != nil && myself.PeerID == peerID {
|
||||||
|
go s.RefreshSelf(peerID, &tools.APIRequest{Admin: true})
|
||||||
|
} else {
|
||||||
|
payload, _ := json.Marshal(map[string]any{"peer_id": peerID})
|
||||||
|
utils.Propalgate(peerID, tools.PropalgationMessage{
|
||||||
|
Action: tools.PB_PLANNER,
|
||||||
|
Payload: payload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline := time.Now().Add(plannerFetchTimeout)
|
||||||
|
for {
|
||||||
|
remaining := s.MissingPeers(checkables)
|
||||||
|
if len(remaining) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wait := time.Until(deadline)
|
||||||
|
if wait <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch, cancelSub := SubscribeUpdates(s.Subs, &s.SubMu, remaining...)
|
||||||
select {
|
select {
|
||||||
case <-ch:
|
case <-ch:
|
||||||
case <-time.After(plannerFetchTimeout):
|
case <-time.After(wait):
|
||||||
}
|
}
|
||||||
cancelSub()
|
cancelSub()
|
||||||
s.ReleaseRefreshOwnership(owned, tmpSession)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allPeers returns the deduplicated list of peer IDs for all checkable resources.
|
||||||
|
func (s *PlannerService) allPeers(res map[string]utils.BookingResource) []string {
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
var out []string
|
||||||
|
for _, r := range res {
|
||||||
|
if _, ok := seen[r.PeerPID]; !ok {
|
||||||
|
seen[r.PeerPID] = struct{}{}
|
||||||
|
out = append(out, r.PeerPID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// evictAfter waits ttl from first insertion then deletes the cache entry and
|
// evictAfter waits ttl from first insertion then deletes the cache entry and
|
||||||
// emits PB_CLOSE_PLANNER so oc-discovery stops streaming for this peer.
|
// emits PB_CLOSE_PLANNER so oc-discovery stops streaming for this peer.
|
||||||
// This is the only path that actually removes an entry from PlannerCache;
|
// This is the only path that actually removes an entry from PlannerCache;
|
||||||
@@ -206,6 +272,10 @@ func SubscribeUpdates[T interface{}](subs map[string][]chan T, mu *sync.RWMutex,
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func (s *PlannerService) Store(peerID string, p *planner.Planner) {
|
func (s *PlannerService) Store(peerID string, p *planner.Planner) {
|
||||||
|
if s == nil {
|
||||||
|
fmt.Println("PLANNER IS NULL")
|
||||||
|
return
|
||||||
|
}
|
||||||
s.Mu.Lock()
|
s.Mu.Lock()
|
||||||
entry := s.Cache[peerID]
|
entry := s.Cache[peerID]
|
||||||
isNew := entry == nil
|
isNew := entry == nil
|
||||||
@@ -216,8 +286,9 @@ func (s *PlannerService) Store(peerID string, p *planner.Planner) {
|
|||||||
go s.EvictAfter(peerID, plannerTTL)
|
go s.EvictAfter(peerID, plannerTTL)
|
||||||
}
|
}
|
||||||
entry.Planner = p
|
entry.Planner = p
|
||||||
|
s.Cache[peerID] = entry
|
||||||
s.Mu.Unlock()
|
s.Mu.Unlock()
|
||||||
utils.Notify[string](&s.SubMu, s.Subs, peerID, peerID)
|
utils.Notify(&s.SubMu, s.Subs, peerID, peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -388,9 +459,15 @@ func (s *PlannerService) checkResourceAvailability(res map[string]utils.BookingR
|
|||||||
s.Mu.RLock()
|
s.Mu.RLock()
|
||||||
entry := s.Cache[r.PeerPID]
|
entry := s.Cache[r.PeerPID]
|
||||||
s.Mu.RUnlock()
|
s.Mu.RUnlock()
|
||||||
if entry == nil || entry.Planner == nil {
|
fmt.Println("Retrieve", r.PeerPID, s.Cache, entry.Planner)
|
||||||
|
if entry == nil {
|
||||||
|
unavailable = append(unavailable, r.ID)
|
||||||
warnings = append(warnings, fmt.Sprintf(
|
warnings = append(warnings, fmt.Sprintf(
|
||||||
"peer %s planner not in cache for resource %s – assuming available", r.PeerPID, r.ID))
|
"resource %s is not available in [%s – %s] : Missing Planner",
|
||||||
|
r.ID, start.Format(time.RFC3339), utils.FormatOptTime(end)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry.Planner == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !s.checkInstance(entry.Planner, r.ID, r.InstanceID, start, end) {
|
if !s.checkInstance(entry.Planner, r.ID, r.InstanceID, start, end) {
|
||||||
@@ -419,17 +496,17 @@ func (s *PlannerService) CheckResourceInstance(peerID, resourceID, instanceID st
|
|||||||
// SubscribePlannerUpdates returns a channel that receives a peerID each time
|
// SubscribePlannerUpdates returns a channel that receives a peerID each time
|
||||||
// one of the given peers' planners is updated.
|
// one of the given peers' planners is updated.
|
||||||
func (s *PlannerService) SubscribePlannerUpdates(peerIDs ...string) (<-chan string, func()) {
|
func (s *PlannerService) SubscribePlannerUpdates(peerIDs ...string) (<-chan string, func()) {
|
||||||
return SubscribeUpdates[string](s.Subs, &s.SubMu, peerIDs...)
|
return SubscribeUpdates(s.Subs, &s.SubMu, peerIDs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscribeWorkflowUpdates returns a channel signalled when the workflow changes.
|
// SubscribeWorkflowUpdates returns a channel signalled when the workflow changes.
|
||||||
func (s *PlannerService) SubscribeWorkflowUpdates(wfID string) (<-chan struct{}, func()) {
|
func (s *PlannerService) SubscribeWorkflowUpdates(wfID string) (<-chan struct{}, func()) {
|
||||||
return SubscribeUpdates[struct{}](s.WorkflowSubs, &s.WorkflowSubMu, wfID)
|
return SubscribeUpdates(s.WorkflowSubs, &s.WorkflowSubMu, wfID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyWorkflow signals all subscribers watching wfID.
|
// NotifyWorkflow signals all subscribers watching wfID.
|
||||||
func (s *PlannerService) NotifyWorkflow(wfID string) {
|
func (s *PlannerService) NotifyWorkflow(wfID string) {
|
||||||
utils.Notify[struct{}](&s.WorkflowSubMu, s.WorkflowSubs, wfID, struct{}{})
|
utils.Notify(&s.WorkflowSubMu, s.WorkflowSubs, wfID, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkInstance checks availability for the specific instance resolved by the
|
// checkInstance checks availability for the specific instance resolved by the
|
||||||
|
|||||||
@@ -40,15 +40,20 @@ type WorkflowSchedule struct {
|
|||||||
DurationS float64 `json:"duration_s" default:"-1"`
|
DurationS float64 `json:"duration_s" default:"-1"`
|
||||||
Cron string `json:"cron,omitempty"`
|
Cron string `json:"cron,omitempty"`
|
||||||
|
|
||||||
BookingMode booking.BookingMode `json:"booking_mode,omitempty"`
|
BookingMode booking.BookingMode `json:"booking_mode,omitempty"`
|
||||||
SelectedInstances workflow.ConfigItem `json:"selected_instances"`
|
SelectedInstances workflow.ConfigItem `json:"selected_instances"`
|
||||||
SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"`
|
SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"`
|
||||||
SelectedBuyings workflow.ConfigItem `json:"selected_buyings"`
|
SelectedBuyings workflow.ConfigItem `json:"selected_buyings"`
|
||||||
SelectedStrategies workflow.ConfigItem `json:"selected_strategies"`
|
SelectedStrategies workflow.ConfigItem `json:"selected_strategies"`
|
||||||
SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
|
SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
|
||||||
|
|
||||||
// Confirm, when true, triggers Schedule() to confirm the drafts held by this session.
|
// Confirm, when true, triggers Schedule() to confirm the drafts held by this session.
|
||||||
Confirm bool `json:"confirm,omitempty"`
|
Confirm bool `json:"confirm,omitempty"`
|
||||||
|
|
||||||
|
// Asap and Preemption override the query-param mode on a per-message basis.
|
||||||
|
// nil means "not set" (keep previous value).
|
||||||
|
Asap *bool `json:"asap,omitempty"`
|
||||||
|
Preemption *bool `json:"preemption,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckResult is the response payload for an availability check.
|
// CheckResult is the response payload for an availability check.
|
||||||
@@ -68,7 +73,6 @@ type CheckResult struct {
|
|||||||
|
|
||||||
// Check verifies whether the requested slot is available across all resource peers.
|
// Check verifies whether the requested slot is available across all resource peers.
|
||||||
func (ws *WorkflowSchedule) Check(wfID string, asap bool, preemption bool, request *tools.APIRequest) (*CheckResult, error) {
|
func (ws *WorkflowSchedule) Check(wfID string, asap bool, preemption bool, request *tools.APIRequest) (*CheckResult, error) {
|
||||||
fmt.Println("CHECK", asap, "/", preemption)
|
|
||||||
obj, code, err := workflow.NewAccessor(request).LoadOne(wfID)
|
obj, code, err := workflow.NewAccessor(request).LoadOne(wfID)
|
||||||
if code != 200 || err != nil {
|
if code != 200 || err != nil {
|
||||||
msg := "could not load workflow " + wfID
|
msg := "could not load workflow " + wfID
|
||||||
@@ -81,7 +85,7 @@ func (ws *WorkflowSchedule) Check(wfID string, asap bool, preemption bool, reque
|
|||||||
|
|
||||||
prepLead := conf.GetConfig().PrepLead()
|
prepLead := conf.GetConfig().PrepLead()
|
||||||
start := ws.Start
|
start := ws.Start
|
||||||
if asap || start.IsZero() {
|
if asap || preemption || start.IsZero() {
|
||||||
start = time.Now().UTC().Add(prepLead)
|
start = time.Now().UTC().Add(prepLead)
|
||||||
} else if start.Before(time.Now().UTC().Add(prepLead)) {
|
} else if start.Before(time.Now().UTC().Add(prepLead)) {
|
||||||
// Explicit date is within the prep window — impossible to guarantee on time.
|
// Explicit date is within the prep window — impossible to guarantee on time.
|
||||||
@@ -240,4 +244,3 @@ func (ws *WorkflowSchedule) GetDates() ([]Schedule, error) {
|
|||||||
}
|
}
|
||||||
return schedule, nil
|
return schedule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"oc-scheduler/infrastructure/planner"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
"cloud.o-forge.io/core/oc-lib/models/booking"
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
||||||
"cloud.o-forge.io/core/oc-lib/models/common/enum"
|
"cloud.o-forge.io/core/oc-lib/models/common/enum"
|
||||||
@@ -14,7 +16,6 @@ import (
|
|||||||
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
|
"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/models/utils"
|
||||||
"cloud.o-forge.io/core/oc-lib/tools"
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
"oc-scheduler/infrastructure/planner"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -31,7 +32,7 @@ type SchedulingResourcesService struct {
|
|||||||
|
|
||||||
var singleton *SchedulingResourcesService
|
var singleton *SchedulingResourcesService
|
||||||
|
|
||||||
func init() {
|
func InitSchedulingResource() {
|
||||||
singleton = &SchedulingResourcesService{}
|
singleton = &SchedulingResourcesService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (s *SessionExecutionsService) sessionIDFilter(field, id string) *dbs.Filter
|
|||||||
|
|
||||||
func (s *SessionExecutionsService) loadSession(dt tools.DataType) []scheduling_resources.SchedulerObject {
|
func (s *SessionExecutionsService) loadSession(dt tools.DataType) []scheduling_resources.SchedulerObject {
|
||||||
results := oclib.NewRequestAdmin(oclib.LibDataEnum(dt), nil).Search(
|
results := oclib.NewRequestAdmin(oclib.LibDataEnum(dt), nil).Search(
|
||||||
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true)
|
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true, 0, 10000)
|
||||||
out := make([]scheduling_resources.SchedulerObject, 0, len(results.Data))
|
out := make([]scheduling_resources.SchedulerObject, 0, len(results.Data))
|
||||||
for _, obj := range results.Data {
|
for _, obj := range results.Data {
|
||||||
out = append(out, scheduling_resources.ToSchedulerObject(dt, obj))
|
out = append(out, scheduling_resources.ToSchedulerObject(dt, obj))
|
||||||
@@ -55,7 +55,7 @@ func (s *SessionExecutionsService) loadSession(dt tools.DataType) []scheduling_r
|
|||||||
func (s *SessionExecutionsService) LoadSessionExecs() []*workflow_execution.WorkflowExecution {
|
func (s *SessionExecutionsService) LoadSessionExecs() []*workflow_execution.WorkflowExecution {
|
||||||
adminReq := &tools.APIRequest{Admin: true}
|
adminReq := &tools.APIRequest{Admin: true}
|
||||||
results, _, _ := workflow_execution.NewAccessor(adminReq).Search(
|
results, _, _ := workflow_execution.NewAccessor(adminReq).Search(
|
||||||
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true)
|
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true, 0, 10000)
|
||||||
out := make([]*workflow_execution.WorkflowExecution, 0)
|
out := make([]*workflow_execution.WorkflowExecution, 0)
|
||||||
for _, obj := range results {
|
for _, obj := range results {
|
||||||
if exec, ok := obj.(*workflow_execution.WorkflowExecution); ok {
|
if exec, ok := obj.(*workflow_execution.WorkflowExecution); ok {
|
||||||
@@ -68,7 +68,7 @@ func (s *SessionExecutionsService) LoadSessionExecs() []*workflow_execution.Work
|
|||||||
func (s *SessionExecutionsService) loadSessionOrder() *order.Order {
|
func (s *SessionExecutionsService) loadSessionOrder() *order.Order {
|
||||||
adminReq := &tools.APIRequest{Admin: true}
|
adminReq := &tools.APIRequest{Admin: true}
|
||||||
results, _, _ := order.NewAccessor(adminReq).Search(
|
results, _, _ := order.NewAccessor(adminReq).Search(
|
||||||
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true)
|
s.sessionIDFilter("executions_id", s.ExecutionsSessionID), "", true, 0, 10000)
|
||||||
for _, obj := range results {
|
for _, obj := range results {
|
||||||
if o, ok := obj.(*order.Order); ok {
|
if o, ok := obj.(*order.Order); ok {
|
||||||
return o
|
return o
|
||||||
@@ -174,8 +174,18 @@ func (s *SessionExecutionsService) upsertDrafts(
|
|||||||
|
|
||||||
func (s *SessionExecutionsService) CleanupSession(request *tools.APIRequest) {
|
func (s *SessionExecutionsService) CleanupSession(request *tools.APIRequest) {
|
||||||
adminReq := &tools.APIRequest{Admin: true}
|
adminReq := &tools.APIRequest{Admin: true}
|
||||||
|
|
||||||
|
// Delete bookings and purchases directly by executions_id.
|
||||||
|
// We cannot rely on execution.Unschedule here because it uses
|
||||||
|
// exec.PeerBookByGraph which is empty during the draft/check phase.
|
||||||
|
for _, dt := range []tools.DataType{tools.BOOKING, tools.PURCHASE_RESOURCE} {
|
||||||
|
for _, obj := range s.loadSession(dt) {
|
||||||
|
scheduling_resources.GetService().Delete(dt, obj, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, exec := range s.LoadSessionExecs() {
|
for _, exec := range s.LoadSessionExecs() {
|
||||||
execution.Unschedule(exec.GetID(), request)
|
execution.UnregisterExecLock(exec.GetID())
|
||||||
workflow_execution.NewAccessor(adminReq).DeleteOne(exec.GetID())
|
workflow_execution.NewAccessor(adminReq).DeleteOne(exec.GetID())
|
||||||
}
|
}
|
||||||
if o := s.loadSessionOrder(); o != nil {
|
if o := s.loadSessionOrder(); o != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ func Notify[T interface{}](mu *sync.RWMutex, registry map[string][]chan T, key s
|
|||||||
|
|
||||||
func Propalgate(peerID string, message tools.PropalgationMessage) {
|
func Propalgate(peerID string, message tools.PropalgationMessage) {
|
||||||
b, _ := json.Marshal(message)
|
b, _ := json.Marshal(message)
|
||||||
|
fmt.Println("Propalgate")
|
||||||
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
FromApp: "oc-scheduler",
|
FromApp: "oc-scheduler",
|
||||||
Datatype: -1,
|
Datatype: -1,
|
||||||
|
|||||||
11
main.go
11
main.go
@@ -3,6 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"oc-scheduler/conf"
|
"oc-scheduler/conf"
|
||||||
"oc-scheduler/infrastructure"
|
"oc-scheduler/infrastructure"
|
||||||
|
"oc-scheduler/infrastructure/planner"
|
||||||
|
"oc-scheduler/infrastructure/scheduling_resources"
|
||||||
_ "oc-scheduler/routers"
|
_ "oc-scheduler/routers"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
@@ -12,6 +14,7 @@ import (
|
|||||||
const appname = "oc-scheduler"
|
const appname = "oc-scheduler"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
o := oclib.GetConfLoader(appname)
|
o := oclib.GetConfLoader(appname)
|
||||||
conf.GetConfig().PrepLeadSeconds = o.GetIntDefault("PREP_LEAD_SECONDS", 120)
|
conf.GetConfig().PrepLeadSeconds = o.GetIntDefault("PREP_LEAD_SECONDS", 120)
|
||||||
conf.GetConfig().KubeHost = o.GetStringDefault("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local")
|
conf.GetConfig().KubeHost = o.GetStringDefault("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local")
|
||||||
@@ -21,11 +24,17 @@ func main() {
|
|||||||
conf.GetConfig().KubeCert = o.GetStringDefault("KUBE_CERT", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNDQVRlZ0F3SUJBZ0lJQUkvSUg2R2Rodm93Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOemN6TVRJM01EazJNQjRYRFRJMk1ETXhNREEzTVRneE5sb1hEVEkzTURNeApNREEzTVRneE5sb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJQTTdBVEZQSmFMMjUrdzAKUU1vZUIxV2hBRW4vWnViM0tSRERrYnowOFhwQWJ2akVpdmdnTkdpdG4wVmVsaEZHamRmNHpBT29Nd1J3M21kbgpYSGtHVDB5alNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUVZLOThaMEMxcFFyVFJSMGVLZHhIa2o0ejFJREFLQmdncWhrak9QUVFEQWdOSkFEQkcKQWlFQXZYWll6Zk9iSUtlWTRtclNsRmt4ZS80a0E4K01ieDc1UDFKRmNlRS8xdGNDSVFDNnM0ZXlZclhQYmNWSgpxZm5EamkrZ1RacGttN0tWSTZTYTlZN2FSRGFabUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZURDQ0FSMmdBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwClpXNTBMV05oUURFM056TXhNamN3T1RZd0hoY05Nall3TXpFd01EY3hPREUyV2hjTk16WXdNekEzTURjeE9ERTIKV2pBak1TRXdId1lEVlFRRERCaHJNM010WTJ4cFpXNTBMV05oUURFM056TXhNamN3T1RZd1dUQVRCZ2NxaGtqTwpQUUlCQmdncWhrak9QUU1CQndOQ0FBUzV1NGVJbStvVnV1SFI0aTZIOU1kVzlyUHdJbFVPNFhIMEJWaDRUTGNlCkNkMnRBbFVXUW5FakxMdlpDWlVaYTlzTlhKOUVtWWt5S0dtQWR2TE9FbUVrbzBJd1FEQU9CZ05WSFE4QkFmOEUKQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVGU3ZmR2RBdGFVSzAwVWRIaW5jUgo1SStNOVNBd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFMY2xtQnR4TnpSVlBvV2hoVEVKSkM1Z3VNSGsvcFZpCjFvYXJ2UVJxTWRKcUFpRUEyR1dNTzlhZFFYTEQwbFZKdHZMVkc1M3I0M0lxMHpEUUQwbTExMVZyL1MwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==")
|
conf.GetConfig().KubeCert = o.GetStringDefault("KUBE_CERT", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNDQVRlZ0F3SUJBZ0lJQUkvSUg2R2Rodm93Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOemN6TVRJM01EazJNQjRYRFRJMk1ETXhNREEzTVRneE5sb1hEVEkzTURNeApNREEzTVRneE5sb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJQTTdBVEZQSmFMMjUrdzAKUU1vZUIxV2hBRW4vWnViM0tSRERrYnowOFhwQWJ2akVpdmdnTkdpdG4wVmVsaEZHamRmNHpBT29Nd1J3M21kbgpYSGtHVDB5alNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUVZLOThaMEMxcFFyVFJSMGVLZHhIa2o0ejFJREFLQmdncWhrak9QUVFEQWdOSkFEQkcKQWlFQXZYWll6Zk9iSUtlWTRtclNsRmt4ZS80a0E4K01ieDc1UDFKRmNlRS8xdGNDSVFDNnM0ZXlZclhQYmNWSgpxZm5EamkrZ1RacGttN0tWSTZTYTlZN2FSRGFabUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZURDQ0FSMmdBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwClpXNTBMV05oUURFM056TXhNamN3T1RZd0hoY05Nall3TXpFd01EY3hPREUyV2hjTk16WXdNekEzTURjeE9ERTIKV2pBak1TRXdId1lEVlFRRERCaHJNM010WTJ4cFpXNTBMV05oUURFM056TXhNamN3T1RZd1dUQVRCZ2NxaGtqTwpQUUlCQmdncWhrak9QUU1CQndOQ0FBUzV1NGVJbStvVnV1SFI0aTZIOU1kVzlyUHdJbFVPNFhIMEJWaDRUTGNlCkNkMnRBbFVXUW5FakxMdlpDWlVaYTlzTlhKOUVtWWt5S0dtQWR2TE9FbUVrbzBJd1FEQU9CZ05WSFE4QkFmOEUKQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVGU3ZmR2RBdGFVSzAwVWRIaW5jUgo1SStNOVNBd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFMY2xtQnR4TnpSVlBvV2hoVEVKSkM1Z3VNSGsvcFZpCjFvYXJ2UVJxTWRKcUFpRUEyR1dNTzlhZFFYTEQwbFZKdHZMVkc1M3I0M0lxMHpEUUQwbTExMVZyL1MwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==")
|
||||||
conf.GetConfig().KubeData = o.GetStringDefault("KUBE_DATA", "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUVkSTRZN3lRU1ZwRGNrblhsQmJEaXBWZHRMWEVsYVBkN3VBZHdBWFFya2xvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFOHpzQk1VOGxvdmJuN0RSQXloNEhWYUVBU2Y5bTV2Y3BFTU9SdlBUeGVrQnUrTVNLK0NBMAphSzJmUlY2V0VVYU4xL2pNQTZnekJIRGVaMmRjZVFaUFRBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=")
|
conf.GetConfig().KubeData = o.GetStringDefault("KUBE_DATA", "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUVkSTRZN3lRU1ZwRGNrblhsQmJEaXBWZHRMWEVsYVBkN3VBZHdBWFFya2xvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFOHpzQk1VOGxvdmJuN0RSQXloNEhWYUVBU2Y5bTV2Y3BFTU9SdlBUeGVrQnUrTVNLK0NBMAphSzJmUlY2V0VVYU4xL2pNQTZnekJIRGVaMmRjZVFaUFRBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=")
|
||||||
|
|
||||||
oclib.InitAPI(appname)
|
oclib.InitAPI(appname, map[string][]string{
|
||||||
|
"/oc/check/:id": {"GET"},
|
||||||
|
"/oc/logs": {"GET"},
|
||||||
|
})
|
||||||
|
go planner.InitPlanner()
|
||||||
|
go scheduling_resources.InitSchedulingResource()
|
||||||
|
|
||||||
go infrastructure.ListenNATS()
|
go infrastructure.ListenNATS()
|
||||||
go infrastructure.InitSelfPlanner()
|
go infrastructure.InitSelfPlanner()
|
||||||
go infrastructure.RecoverDraftExecutions()
|
go infrastructure.RecoverDraftExecutions()
|
||||||
go infrastructure.WatchExecutions()
|
go infrastructure.WatchExecutions()
|
||||||
|
|
||||||
beego.Run()
|
beego.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
oc-scheduler
BIN
oc-scheduler
Binary file not shown.
@@ -145,7 +145,7 @@ func init() {
|
|||||||
beego.GlobalControllerRouter["oc-scheduler/controllers:WorkflowSchedulerController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:WorkflowSchedulerController"],
|
beego.GlobalControllerRouter["oc-scheduler/controllers:WorkflowSchedulerController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:WorkflowSchedulerController"],
|
||||||
beego.ControllerComments{
|
beego.ControllerComments{
|
||||||
Method: "SearchScheduledDraftOrder",
|
Method: "SearchScheduledDraftOrder",
|
||||||
Router: `/:id/order`,
|
Router: `/order/:id`,
|
||||||
AllowHTTPMethods: []string{"get"},
|
AllowHTTPMethods: []string{"get"},
|
||||||
MethodParams: param.Make(),
|
MethodParams: param.Make(),
|
||||||
Filters: nil,
|
Filters: nil,
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ func init() {
|
|||||||
|
|
||||||
beego.AddNamespace(ns)
|
beego.AddNamespace(ns)
|
||||||
|
|
||||||
// WebSocket route registered outside the Beego pipeline to avoid the
|
// WebSocket routes registered outside the Beego pipeline to avoid the
|
||||||
// spurious WriteHeader that prevents the 101 Switching Protocols upgrade.
|
// spurious WriteHeader that prevents the 101 Switching Protocols upgrade.
|
||||||
beego.Handler("/oc/:id/check", http.HandlerFunc(controllers.CheckStreamHandler))
|
beego.Handler("/oc/check/:id", http.HandlerFunc(controllers.CheckStreamHandler))
|
||||||
|
beego.Handler("/oc/logs", http.HandlerFunc(controllers.LogsStreamHandler))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/order/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"oc-scheduler/controllersWorkflowSchedulerController"
|
||||||
|
],
|
||||||
|
"description": "search draft order for a workflow\n\u003cbr\u003e",
|
||||||
|
"operationId": "WorkflowSchedulerController.SearchScheduledDraftOrder",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "id execution",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{workspace} models.workspace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/verification/": {
|
"/verification/": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -388,29 +411,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/{id}/order": {
|
|
||||||
"get": {
|
|
||||||
"tags": [
|
|
||||||
"oc-scheduler/controllersWorkflowSchedulerController"
|
|
||||||
],
|
|
||||||
"description": "schedule workflow\n\u003cbr\u003e",
|
|
||||||
"operationId": "WorkflowSchedulerController.SearchScheduledDraftOrder",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"in": "path",
|
|
||||||
"name": "id",
|
|
||||||
"description": "id execution",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "{workspace} models.workspace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|||||||
@@ -31,23 +31,6 @@ paths:
|
|||||||
description: ""
|
description: ""
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/map[string]interface{}'
|
$ref: '#/definitions/map[string]interface{}'
|
||||||
/{id}/order:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- oc-scheduler/controllersWorkflowSchedulerController
|
|
||||||
description: |-
|
|
||||||
schedule workflow
|
|
||||||
<br>
|
|
||||||
operationId: WorkflowSchedulerController.SearchScheduledDraftOrder
|
|
||||||
parameters:
|
|
||||||
- in: path
|
|
||||||
name: id
|
|
||||||
description: id execution
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: '{workspace} models.workspace'
|
|
||||||
/booking/:
|
/booking/:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -226,6 +209,23 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: '{workspace} models.workspace'
|
description: '{workspace} models.workspace'
|
||||||
|
/order/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- oc-scheduler/controllersWorkflowSchedulerController
|
||||||
|
description: |-
|
||||||
|
search draft order for a workflow
|
||||||
|
<br>
|
||||||
|
operationId: WorkflowSchedulerController.SearchScheduledDraftOrder
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: id execution
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{workspace} models.workspace'
|
||||||
/verification/:
|
/verification/:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
2
ws.go
2
ws.go
@@ -23,7 +23,7 @@ func main() {
|
|||||||
// ws://localhost:8090/oc/<workflow-id>/check
|
// ws://localhost:8090/oc/<workflow-id>/check
|
||||||
// ws://localhost:8090/oc/<workflow-id>/check?as_possible=true
|
// ws://localhost:8090/oc/<workflow-id>/check?as_possible=true
|
||||||
// ws://localhost:8090/oc/<workflow-id>/check?as_possible=true&preemption=true
|
// ws://localhost:8090/oc/<workflow-id>/check?as_possible=true&preemption=true
|
||||||
url := "ws://localhost:8090/oc/58314c99-c595-4ca2-8b5e-822a6774efed/check?as_possible=true"
|
url := "ws://localhost:8090/oc/check/58314c99-c595-4ca2-8b5e-822a6774efed?as_possible=true"
|
||||||
token := ""
|
token := ""
|
||||||
// Body JSON envoyé comme premier message WebSocket (WorkflowSchedule).
|
// Body JSON envoyé comme premier message WebSocket (WorkflowSchedule).
|
||||||
// Seuls start + duration_s sont requis si as_possible=true.
|
// Seuls start + duration_s sont requis si as_possible=true.
|
||||||
|
|||||||
Reference in New Issue
Block a user