Add verification
This commit is contained in:
68
controllers/execution_verification.go
Normal file
68
controllers/execution_verification.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"oc-scheduler/infrastructure"
|
||||
|
||||
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/resources/native_tools"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// Operations about workspace
|
||||
type ExecutionVerificationController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// @Title GetAll
|
||||
// @Description find verification by id
|
||||
// @Param is_draft query string false "draft wished"
|
||||
// @Success 200 {booking} models.booking
|
||||
// @router / [get]
|
||||
func (o *ExecutionVerificationController) GetAll() {
|
||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||
isDraft := o.Ctx.Input.Query("is_draft")
|
||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.EXECUTION_VERIFICATION), user, peerID, groups, nil).LoadAll(isDraft == "true")
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Get
|
||||
// @Description find verification by id
|
||||
// @Param id path string true "the id you want to get"
|
||||
// @Success 200 {booking} models.booking
|
||||
// @router /:id [get]
|
||||
func (o *ExecutionVerificationController) Get() {
|
||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||
id := o.Ctx.Input.Param(":id")
|
||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.EXECUTION_VERIFICATION), user, peerID, groups, nil).LoadOne(id)
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Update
|
||||
// @Description create computes
|
||||
// @Param id path string true "the compute id you want to get"
|
||||
// @Param body body models.compute true "The compute content"
|
||||
// @Success 200 {compute} models.compute
|
||||
// @router /:id [put]
|
||||
func (o *ExecutionVerificationController) Put() {
|
||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||
// store and return Id or post with UUID
|
||||
var res map[string]interface{}
|
||||
id := o.Ctx.Input.Param(":id")
|
||||
json.Unmarshal(o.Ctx.Input.CopyBody(10000), &res)
|
||||
data := oclib.NewRequest(oclib.LibDataEnum(oclib.EXECUTION_VERIFICATION), user, peerID, groups, nil).UpdateOne(res, id)
|
||||
if data.Err == "" && data.Data != nil && data.Data.(*execution_verification.ExecutionVerification).Validate {
|
||||
data, _ := json.Marshal(&native_tools.WorkflowEventParams{
|
||||
WorkflowResourceID: data.Data.(*execution_verification.ExecutionVerification).WorkflowID,
|
||||
})
|
||||
infrastructure.EmitNATS(peerID, tools.PropalgationMessage{
|
||||
Action: tools.PubSubAction(tools.WORKFLOW_EVENT),
|
||||
DataType: tools.EXECUTION_VERIFICATION.EnumIndex(),
|
||||
Payload: data,
|
||||
})
|
||||
}
|
||||
o.Data["json"] = data
|
||||
o.ServeJSON()
|
||||
}
|
||||
@@ -39,18 +39,8 @@ func (o *WorkflowSchedulerController) Schedule() {
|
||||
var resp *infrastructure.WorkflowSchedule
|
||||
json.Unmarshal(o.Ctx.Input.CopyBody(100000), &resp)
|
||||
|
||||
caller := tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{ // paths to call other OC services
|
||||
tools.PEER: {
|
||||
tools.POST: "/status/",
|
||||
},
|
||||
tools.BOOKING: {
|
||||
tools.GET: "/booking/check/:id/:start_date/:end_date",
|
||||
tools.POST: "/booking/",
|
||||
},
|
||||
})
|
||||
|
||||
logger.Info().Msg("Booking for " + wfId)
|
||||
req := oclib.NewRequestAdmin(collection, caller)
|
||||
req := oclib.NewRequestAdmin(collection, nil)
|
||||
// req := oclib.NewRequest(collection, user, peerID, groups, caller)
|
||||
resp.UUID = uuid.New().String()
|
||||
fmt.Println(user, peerID, groups)
|
||||
@@ -58,7 +48,7 @@ func (o *WorkflowSchedulerController) Schedule() {
|
||||
Username: user,
|
||||
PeerID: peerID,
|
||||
Groups: groups,
|
||||
Caller: caller,
|
||||
Caller: nil,
|
||||
Admin: true,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -87,13 +77,24 @@ var wsUpgrader = gorillaws.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
|
||||
// CheckStreamHandler is a plain http.HandlerFunc (registered via beego.Handler
|
||||
// to avoid Beego's WriteHeader interference with the WebSocket upgrade).
|
||||
// Path: /oc/:id/check → parts = ["", "oc", "<id>", "check"]
|
||||
// @Title CheckStream
|
||||
// @Description WebSocket stream for slot availability checking.
|
||||
// @Param id path string true "workflow id"
|
||||
// @Param as_possible query bool false "search from now"
|
||||
// @Param preemption query bool false "force-valid, surface warnings"
|
||||
// @router /:id/check [get]
|
||||
func (o *WorkflowSchedulerController) CheckStream() {
|
||||
CheckStreamHandler(o.Ctx.ResponseWriter, o.Ctx.Request)
|
||||
}
|
||||
|
||||
// CheckStreamHandler is the WebSocket handler for slot availability checking.
|
||||
// It is invoked via the CheckStream controller method.
|
||||
// Query params: as_possible=true, preemption=true
|
||||
func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(strings.TrimSuffix(r.URL.Path, "/"), "/")
|
||||
wfID := parts[len(parts)-2] // second-to-last segment
|
||||
wfID := strings.TrimSuffix(
|
||||
strings.TrimPrefix(r.URL.Path, "/oc/"),
|
||||
"/check",
|
||||
)
|
||||
|
||||
q := r.URL.Query()
|
||||
asap := q.Get("as_possible") == "true"
|
||||
@@ -136,20 +137,23 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
plannerCh, plannerUnsub := infrastructure.SubscribePlannerUpdates(watchedPeers)
|
||||
wfCh, wfUnsub := infrastructure.SubscribeWorkflowUpdates(wfID)
|
||||
|
||||
// Cleanup on exit: cancel subscriptions, evict planner cache entries,
|
||||
// signal PB_CLOSE_PLANNER on NATS for each peer that was being watched.
|
||||
// Unique ID for this check session — used to track refresh ownership.
|
||||
sessionID := uuid.New().String()
|
||||
|
||||
// Request a fresh planner snapshot for every concerned peer.
|
||||
// The first session to claim a peer becomes its refresh owner; others skip
|
||||
// the duplicate PB_PLANNER emission. ownedPeers grows if the workflow's
|
||||
// peer list changes (wfCh).
|
||||
ownedPeers := infrastructure.RequestPlannerRefresh(watchedPeers, sessionID)
|
||||
|
||||
// Cleanup on exit (clean or forced): release refresh ownership for the
|
||||
// peers this session claimed, which resets Refreshing state and emits
|
||||
// PB_CLOSE_PLANNER so oc-discovery stops the planner stream.
|
||||
defer func() {
|
||||
conn.Close()
|
||||
plannerUnsub()
|
||||
wfUnsub()
|
||||
for _, peer := range watchedPeers {
|
||||
if b, err := json.Marshal(map[string]interface{}{"peer_id": peer}); err == nil {
|
||||
infrastructure.EmitNATS(peer, tools.PropalgationMessage{
|
||||
Action: tools.PB_CLOSE_PLANNER,
|
||||
Payload: b,
|
||||
})
|
||||
}
|
||||
}
|
||||
infrastructure.ReleaseRefreshOwnership(ownedPeers, sessionID)
|
||||
}()
|
||||
|
||||
push := func() error {
|
||||
@@ -166,20 +170,38 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Detect client-side close in a separate goroutine.
|
||||
// Read loop: detect client-side close and parse schedule parameter
|
||||
// updates (date changes, booking mode changes, …) sent by the client.
|
||||
updateCh := make(chan infrastructure.WorkflowSchedule, 1)
|
||||
closeCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(closeCh)
|
||||
for {
|
||||
if _, _, err := conn.ReadMessage(); err != nil {
|
||||
var updated infrastructure.WorkflowSchedule
|
||||
if err := conn.ReadJSON(&updated); err != nil {
|
||||
// Connection closed or unrecoverable read error.
|
||||
return
|
||||
}
|
||||
// Drop the oldest pending update if the consumer hasn't caught up.
|
||||
select {
|
||||
case updateCh <- updated:
|
||||
default:
|
||||
<-updateCh
|
||||
updateCh <- updated
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Stream loop.
|
||||
for {
|
||||
select {
|
||||
case updated := <-updateCh:
|
||||
// The client changed the requested date/params: adopt the new
|
||||
// schedule and re-run the check immediately.
|
||||
ws = updated
|
||||
if err := push(); err != nil {
|
||||
return
|
||||
}
|
||||
case <-wfCh:
|
||||
// The workflow was modified: refresh the peer list and re-subscribe
|
||||
// so the stream watches the correct set of planners going forward.
|
||||
@@ -187,11 +209,15 @@ func CheckStreamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
plannerUnsub()
|
||||
watchedPeers = newPeers
|
||||
plannerCh, plannerUnsub = infrastructure.SubscribePlannerUpdates(newPeers)
|
||||
// Claim refresh ownership for any newly added peers.
|
||||
newOwned := infrastructure.RequestPlannerRefresh(newPeers, sessionID)
|
||||
ownedPeers = append(ownedPeers, newOwned...)
|
||||
}
|
||||
if err := push(); err != nil {
|
||||
return
|
||||
}
|
||||
case <-plannerCh:
|
||||
// A planner snapshot arrived (or was evicted): re-evaluate.
|
||||
if err := push(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module oc-scheduler
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260304145747-e03a0d3dd0aa
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312105633-a30173921f67
|
||||
github.com/beego/beego/v2 v2.3.8
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/robfig/cron v1.2.0
|
||||
|
||||
12
go.sum
12
go.sum
@@ -16,6 +16,18 @@ cloud.o-forge.io/core/oc-lib v0.0.0-20260224093610-a9ebad78f3a8 h1:xoC5PAz1469Qx
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260224093610-a9ebad78f3a8/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260304145747-e03a0d3dd0aa h1:1wCpI4dwN1pj6MlpJ7/WifhHVHmCE4RU+9klwqgo/bk=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260304145747-e03a0d3dd0aa/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260311072518-933b7147e908 h1:1jz3xI/u2FzCG8phY7ShqADrmCj0mlrdjbdNUosSwgs=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260311072518-933b7147e908/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260311084029-97bfb0582a99 h1:60BGJeR9uvpDwvNeWqVBnB2JjWLOZv16sUGZjzXSQlg=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260311084029-97bfb0582a99/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312073634-2c9c42dd516a h1:oCkb9l/Cvn0x6iicxIydrjfCNU+UHhKuklFgfzDa174=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312073634-2c9c42dd516a/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312083310-f5e199132416 h1:QHR5pzCI/HUawu8pst5Ggio6WPCUUf8XYjNMVk8kSqo=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312083310-f5e199132416/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312104524-e28b79ac0d62 h1:sHzacZxPIKHyjL4EkgG/c7MI8gM1xmLdhaoUx2ZsH+M=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312104524-e28b79ac0d62/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312105633-a30173921f67 h1:x6klvxiRpU1KcvmygIcHGDHFW3CbWC05El6Fryvr3uo=
|
||||
cloud.o-forge.io/core/oc-lib v0.0.0-20260312105633-a30173921f67/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/beego/beego/v2 v2.3.8 h1:wplhB1pF4TxR+2SS4PUej8eDoH4xGfxuHfS7wAk9VBc=
|
||||
github.com/beego/beego/v2 v2.3.8/go.mod h1:8vl9+RrXqvodrl9C8yivX1e6le6deCK6RWeq8R7gTTg=
|
||||
|
||||
@@ -29,8 +29,20 @@ const plannerTTL = 24 * time.Hour
|
||||
// Planner cache — protected by plannerMu
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// plannerEntry wraps a planner snapshot with refresh-ownership tracking.
|
||||
// At most one check session may be the "refresh owner" of a given peer's
|
||||
// planner at a time: it emits PB_PLANNER to request a fresh snapshot from
|
||||
// oc-discovery and, on close (clean or forced), emits PB_CLOSE_PLANNER to
|
||||
// release the stream. Any subsequent session that needs the same peer's
|
||||
// planner will see Refreshing=true and skip the duplicate request.
|
||||
type plannerEntry struct {
|
||||
Planner *planner.Planner
|
||||
Refreshing bool // true while a PB_PLANNER request is in flight
|
||||
RefreshOwner string // session UUID that initiated the current refresh
|
||||
}
|
||||
|
||||
var plannerMu sync.RWMutex
|
||||
var PlannerCache = map[string]*planner.Planner{}
|
||||
var PlannerCache = map[string]*plannerEntry{}
|
||||
var plannerAddedAt = map[string]time.Time{} // peerID → first-seen timestamp
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -104,29 +116,39 @@ func notify(mu *sync.RWMutex, registry map[string][]chan struct{}, key string) {
|
||||
// Cache helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// storePlanner inserts or updates a planner for peerID.
|
||||
// storePlanner inserts or updates the planner snapshot for peerID.
|
||||
// On first insertion it schedules an automatic eviction after plannerTTL.
|
||||
// Existing refresh-ownership state (Refreshing / RefreshOwner) is preserved
|
||||
// so that an in-flight request is not inadvertently reset.
|
||||
// All subscribers interested in this peer are notified.
|
||||
func storePlanner(peerID string, p *planner.Planner) {
|
||||
plannerMu.Lock()
|
||||
isNew := PlannerCache[peerID] == nil
|
||||
PlannerCache[peerID] = p
|
||||
entry := PlannerCache[peerID]
|
||||
isNew := entry == nil
|
||||
if isNew {
|
||||
entry = &plannerEntry{}
|
||||
PlannerCache[peerID] = entry
|
||||
plannerAddedAt[peerID] = time.Now()
|
||||
go evictAfter(peerID, plannerTTL)
|
||||
}
|
||||
entry.Planner = p
|
||||
plannerMu.Unlock()
|
||||
notifyPlannerWatchers(peerID)
|
||||
}
|
||||
|
||||
// evictAfter waits ttl from the first-seen time for peerID then emits a
|
||||
// PB_CLOSE_PLANNER event, which removes the entry from the cache and notifies
|
||||
// NATS.
|
||||
// evictAfter waits ttl from first insertion then deletes the cache entry and
|
||||
// emits PB_CLOSE_PLANNER so oc-discovery stops streaming for this peer.
|
||||
// This is the only path that actually removes an entry from PlannerCache;
|
||||
// session close (ReleaseRefreshOwnership) only resets ownership state.
|
||||
func evictAfter(peerID string, ttl time.Duration) {
|
||||
time.Sleep(ttl)
|
||||
plannerMu.RLock()
|
||||
plannerMu.Lock()
|
||||
_, exists := PlannerCache[peerID]
|
||||
plannerMu.RUnlock()
|
||||
if exists {
|
||||
delete(PlannerCache, peerID)
|
||||
delete(plannerAddedAt, peerID)
|
||||
}
|
||||
plannerMu.Unlock()
|
||||
if exists {
|
||||
EmitNATS(peerID, tools.PropalgationMessage{Action: tools.PB_CLOSE_PLANNER})
|
||||
}
|
||||
@@ -137,12 +159,11 @@ func evictAfter(peerID string, ttl time.Duration) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func EmitNATS(peerID string, message tools.PropalgationMessage) {
|
||||
// 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.
|
||||
if message.Action == tools.PB_CLOSE_PLANNER {
|
||||
plannerMu.Lock()
|
||||
delete(PlannerCache, peerID)
|
||||
delete(plannerAddedAt, peerID)
|
||||
plannerMu.Unlock()
|
||||
notifyPlannerWatchers(peerID) // let streams re-evaluate (will warn "no planner")
|
||||
notifyPlannerWatchers(peerID)
|
||||
}
|
||||
b, _ := json.Marshal(message)
|
||||
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||
@@ -309,6 +330,17 @@ func ListenNATS() {
|
||||
// considered in the target WorkflowExecution (BookingsState / PurchasesState).
|
||||
// WORKFLOW_EXECUTION → the execution reached SCHEDULED; confirm all
|
||||
// local draft bookings and purchases listed in its states.
|
||||
tools.PLANNER_EXECUTION: func(resp tools.NATSResponse) {
|
||||
m := map[string]interface{}{}
|
||||
p := planner.Planner{}
|
||||
if err := json.Unmarshal(resp.Payload, &m); err != nil {
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(resp.Payload, &p); err != nil {
|
||||
return
|
||||
}
|
||||
storePlanner(fmt.Sprintf("%v", m["peer_id"]), &p)
|
||||
},
|
||||
tools.PROPALGATION_EVENT: func(resp tools.NATSResponse) {
|
||||
if resp.FromApp != "oc-discovery" {
|
||||
return
|
||||
@@ -318,16 +350,6 @@ func ListenNATS() {
|
||||
return
|
||||
}
|
||||
switch prop.Action {
|
||||
case tools.PB_PLANNER:
|
||||
m := map[string]interface{}{}
|
||||
p := planner.Planner{}
|
||||
if err := json.Unmarshal(prop.Payload, &m); err != nil {
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(prop.Payload, &p); err != nil {
|
||||
return
|
||||
}
|
||||
storePlanner(fmt.Sprintf("%v", m["peer_id"]), &p)
|
||||
case tools.PB_CONSIDERS:
|
||||
switch tools.DataType(prop.DataType) {
|
||||
case tools.BOOKING, tools.PURCHASE_RESOURCE:
|
||||
@@ -379,9 +401,9 @@ func ListenNATS() {
|
||||
}
|
||||
// Verify the slot is free in our planner (if we have one).
|
||||
plannerMu.RLock()
|
||||
p := PlannerCache[self.PeerID]
|
||||
selfEntry := PlannerCache[self.PeerID]
|
||||
plannerMu.RUnlock()
|
||||
if p != nil && !checkInstance(p, bk.ResourceID, bk.InstanceID, bk.ExpectedStartDate, bk.ExpectedEndDate) {
|
||||
if selfEntry != nil && selfEntry.Planner != nil && !checkInstance(selfEntry.Planner, bk.ResourceID, bk.InstanceID, bk.ExpectedStartDate, bk.ExpectedEndDate) {
|
||||
fmt.Println("ListenNATS: booking conflicts with local planner, discarding")
|
||||
return
|
||||
}
|
||||
@@ -598,6 +620,60 @@ func refreshSelfPlanner(peerID string, request *tools.APIRequest) {
|
||||
// Planner broadcast
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// RequestPlannerRefresh asks oc-discovery for a fresh planner snapshot for
|
||||
// each peer in peerIDs. Only the first session to request a given peer becomes
|
||||
// its "refresh owner": subsequent sessions see Refreshing=true and skip the
|
||||
// duplicate PB_PLANNER emission. Returns the subset of peerIDs for which this
|
||||
// session claimed ownership (needed to release on close).
|
||||
func RequestPlannerRefresh(peerIDs []string, sessionID string) []string {
|
||||
var owned []string
|
||||
for _, peerID := range peerIDs {
|
||||
plannerMu.Lock()
|
||||
entry := PlannerCache[peerID]
|
||||
if entry == nil {
|
||||
entry = &plannerEntry{}
|
||||
PlannerCache[peerID] = entry
|
||||
plannerAddedAt[peerID] = time.Now()
|
||||
go evictAfter(peerID, plannerTTL)
|
||||
}
|
||||
shouldRequest := !entry.Refreshing
|
||||
if shouldRequest {
|
||||
entry.Refreshing = true
|
||||
entry.RefreshOwner = sessionID
|
||||
}
|
||||
plannerMu.Unlock()
|
||||
if shouldRequest {
|
||||
owned = append(owned, peerID)
|
||||
payload, _ := json.Marshal(map[string]any{"peer_id": peerID})
|
||||
EmitNATS(peerID, tools.PropalgationMessage{
|
||||
Action: tools.PB_PLANNER,
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
return owned
|
||||
}
|
||||
|
||||
// ReleaseRefreshOwnership is called when a check session closes (clean or
|
||||
// forced). For each peer this session owns, it resets the refresh state and
|
||||
// emits PB_CLOSE_PLANNER so oc-discovery stops the planner stream.
|
||||
// The planner data itself stays in the cache until TTL eviction.
|
||||
func ReleaseRefreshOwnership(peerIDs []string, sessionID string) {
|
||||
for _, peerID := range peerIDs {
|
||||
plannerMu.Lock()
|
||||
if entry := PlannerCache[peerID]; entry != nil && entry.RefreshOwner == sessionID {
|
||||
entry.Refreshing = false
|
||||
entry.RefreshOwner = ""
|
||||
}
|
||||
plannerMu.Unlock()
|
||||
payload, _ := json.Marshal(map[string]any{"peer_id": peerID})
|
||||
EmitNATS(peerID, tools.PropalgationMessage{
|
||||
Action: tools.PB_CLOSE_PLANNER,
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastPlanner iterates the storage and compute peers of the given workflow
|
||||
// and, for each peer not yet in the cache, emits a PB_PLANNER propagation so
|
||||
// downstream consumers (oc-discovery, other schedulers) refresh their state.
|
||||
@@ -631,7 +707,8 @@ func broadcastPlanner(wf *workflow.Workflow) {
|
||||
cached := PlannerCache[p.PeerID]
|
||||
plannerMu.RUnlock()
|
||||
|
||||
if cached == nil {
|
||||
// Only request if no snapshot and no refresh already in flight.
|
||||
if cached == nil || (cached.Planner == nil && !cached.Refreshing) {
|
||||
payload, err := json.Marshal(map[string]interface{}{"peer_id": p.PeerID})
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -520,14 +520,14 @@ func collectBookingResources(wf *workflow.Workflow, selectedInstances workflow.C
|
||||
func checkResourceAvailability(res []bookingResource, start time.Time, end *time.Time) (unavailable []string, warnings []string) {
|
||||
for _, r := range res {
|
||||
plannerMu.RLock()
|
||||
p := PlannerCache[r.peerID]
|
||||
entry := PlannerCache[r.peerID]
|
||||
plannerMu.RUnlock()
|
||||
if p == nil {
|
||||
if entry == nil || entry.Planner == nil {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"peer %s planner not in cache for resource %s – assuming available", r.peerID, r.id))
|
||||
continue
|
||||
}
|
||||
if !checkInstance(p, r.id, r.instanceID, start, end) {
|
||||
if !checkInstance(entry.Planner, r.id, r.instanceID, start, end) {
|
||||
unavailable = append(unavailable, r.id)
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"resource %s is not available in [%s – %s]",
|
||||
|
||||
@@ -43,6 +43,33 @@ func init() {
|
||||
Filters: nil,
|
||||
Params: nil})
|
||||
|
||||
beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"],
|
||||
beego.ControllerComments{
|
||||
Method: "GetAll",
|
||||
Router: `/`,
|
||||
AllowHTTPMethods: []string{"get"},
|
||||
MethodParams: param.Make(),
|
||||
Filters: nil,
|
||||
Params: nil})
|
||||
|
||||
beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"],
|
||||
beego.ControllerComments{
|
||||
Method: "Get",
|
||||
Router: `/:id`,
|
||||
AllowHTTPMethods: []string{"get"},
|
||||
MethodParams: param.Make(),
|
||||
Filters: nil,
|
||||
Params: nil})
|
||||
|
||||
beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:ExecutionVerificationController"],
|
||||
beego.ControllerComments{
|
||||
Method: "Put",
|
||||
Router: `/:id`,
|
||||
AllowHTTPMethods: []string{"put"},
|
||||
MethodParams: param.Make(),
|
||||
Filters: nil,
|
||||
Params: nil})
|
||||
|
||||
beego.GlobalControllerRouter["oc-scheduler/controllers:LokiController"] = append(beego.GlobalControllerRouter["oc-scheduler/controllers:LokiController"],
|
||||
beego.ControllerComments{
|
||||
Method: "GetLogs",
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"oc-scheduler/controllers"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
@@ -29,6 +28,11 @@ func init() {
|
||||
&controllers.BookingController{},
|
||||
),
|
||||
),
|
||||
beego.NSNamespace("/verification",
|
||||
beego.NSInclude(
|
||||
&controllers.ExecutionVerificationController{},
|
||||
),
|
||||
),
|
||||
beego.NSNamespace("/execution",
|
||||
beego.NSInclude(
|
||||
&controllers.WorkflowExecutionController{},
|
||||
@@ -42,7 +46,4 @@ func init() {
|
||||
)
|
||||
|
||||
beego.AddNamespace(ns)
|
||||
|
||||
// Route WebSocket hors du pipeline Beego pour éviter le WriteHeader parasite
|
||||
beego.Handler("/oc/:id/check", http.HandlerFunc(controllers.CheckStreamHandler))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user