Correct
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/models/booking"
|
||||
"cloud.o-forge.io/core/oc-lib/models/booking/planner"
|
||||
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
|
||||
"cloud.o-forge.io/core/oc-lib/models/common"
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||
@@ -594,8 +595,36 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, request *tools.APIRequest) (bool, float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
|
||||
// preemptDelay is the minimum lead time granted before a preempted booking starts.
|
||||
const preemptDelay = 30 * time.Second
|
||||
|
||||
// Planify computes the scheduled start/end for every resource in the workflow.
|
||||
//
|
||||
// bookingMode controls availability checking when p (a live planner snapshot) is provided:
|
||||
// - PREEMPTED : start from now+preemptDelay regardless of existing load.
|
||||
// - WHEN_POSSIBLE: start from max(now, start); if a slot conflicts, slide to the next free window.
|
||||
// - PLANNED : use start as-is; return an error if the slot is not available.
|
||||
//
|
||||
// Passing p = nil skips all availability checks (useful for sub-workflow recursion).
|
||||
func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, p planner.PlannerITF, request *tools.APIRequest) (bool, float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
|
||||
// 1. Adjust global start based on booking mode.
|
||||
now := time.Now()
|
||||
switch booking.BookingMode(bookingMode) {
|
||||
case booking.PREEMPTED:
|
||||
if earliest := now.Add(preemptDelay); start.Before(earliest) {
|
||||
start = earliest
|
||||
}
|
||||
case booking.WHEN_POSSIBLE:
|
||||
if start.Before(now) {
|
||||
start = now
|
||||
}
|
||||
// PLANNED: honour the caller's start date as-is.
|
||||
}
|
||||
|
||||
priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
|
||||
var err error
|
||||
|
||||
// 2. Plan processings first so we can derive the total workflow duration.
|
||||
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, wf.Graph.IsProcessing,
|
||||
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
|
||||
d, err := wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(),
|
||||
@@ -612,6 +641,11 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
if err != nil {
|
||||
return false, 0, priceds, nil, err
|
||||
}
|
||||
|
||||
// Total workflow duration used as the booking window for compute/storage.
|
||||
// Returns -1 if any processing is a service (open-ended).
|
||||
workflowDuration := common.GetPlannerLongestTime(priceds)
|
||||
|
||||
if _, priceds, err = plan[resources.ResourceInterface](tools.NATIVE_TOOL, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
|
||||
wf.Graph.IsNativeTool, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
|
||||
return start, 0, nil
|
||||
@@ -628,11 +662,13 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
}); err != nil {
|
||||
return false, 0, priceds, nil, err
|
||||
}
|
||||
|
||||
// 3. Compute/storage: duration = total workflow duration (conservative bound).
|
||||
for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage,
|
||||
tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
|
||||
if _, priceds, err = plan[resources.ResourceInterface](k, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
|
||||
f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
|
||||
nearestStart, longestDuration, err := wf.Graph.GetAverageTimeRelatedToProcessingActivity(ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
|
||||
nearestStart, _, err := wf.Graph.GetAverageTimeRelatedToProcessingActivity(ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
|
||||
if f(i) {
|
||||
_, r = i.GetResource()
|
||||
}
|
||||
@@ -640,17 +676,21 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
}, *instances.Get(res.GetID()), *partnerships.Get(res.GetID()),
|
||||
*buyings.Get(res.GetID()), *strategies.Get(res.GetID()), bookingMode, request)
|
||||
if err != nil {
|
||||
return start, longestDuration, err
|
||||
return start, workflowDuration, err
|
||||
}
|
||||
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration, nil
|
||||
return start.Add(time.Duration(nearestStart) * time.Second), workflowDuration, nil
|
||||
}, func(started time.Time, duration float64) (*time.Time, error) {
|
||||
if duration < 0 {
|
||||
return nil, nil // service: open-ended booking
|
||||
}
|
||||
s := started.Add(time.Duration(duration) * time.Second)
|
||||
return &s, nil
|
||||
}); err != nil {
|
||||
return false, 0, priceds, nil, err
|
||||
}
|
||||
}
|
||||
longest := common.GetPlannerLongestTime(priceds)
|
||||
|
||||
longest := workflowDuration
|
||||
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, instances, partnerships, buyings, strategies,
|
||||
bookingMode, wf, priceds, request, wf.Graph.IsWorkflow,
|
||||
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
|
||||
@@ -660,7 +700,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
if code != 200 || err != nil {
|
||||
return start, longest, err
|
||||
}
|
||||
_, neoLongest, priceds2, _, err := r.(*Workflow).Planify(start, end, instances, partnerships, buyings, strategies, bookingMode, request)
|
||||
_, neoLongest, priceds2, _, err := r.(*Workflow).Planify(start, end, instances, partnerships, buyings, strategies, bookingMode, nil, request)
|
||||
// should ... import priced
|
||||
if err != nil {
|
||||
return start, longest, err
|
||||
@@ -685,6 +725,19 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
}); err != nil {
|
||||
return false, 0, priceds, nil, err
|
||||
}
|
||||
|
||||
// 4. Availability check against the live planner (skipped for PREEMPTED and sub-workflows).
|
||||
if p != nil && booking.BookingMode(bookingMode) != booking.PREEMPTED {
|
||||
slide, err := plannerAvailabilitySlide(p, priceds, booking.BookingMode(bookingMode))
|
||||
if err != nil {
|
||||
return false, 0, priceds, nil, err
|
||||
}
|
||||
if slide > 0 {
|
||||
// Re-plan from the corrected start; pass nil planner to avoid infinite recursion.
|
||||
return wf.Planify(start.Add(slide), end, instances, partnerships, buyings, strategies, bookingMode, nil, request)
|
||||
}
|
||||
}
|
||||
|
||||
isPreemptible := true
|
||||
for _, first := range wf.GetFirstItems() {
|
||||
_, res := first.GetResource()
|
||||
@@ -696,6 +749,36 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigIte
|
||||
return isPreemptible, longest, priceds, wf, nil
|
||||
}
|
||||
|
||||
// plannerAvailabilitySlide checks all compute/storage resources in priceds against the planner.
|
||||
// For PLANNED mode it returns an error immediately on the first conflict.
|
||||
// For WHEN_POSSIBLE it returns the maximum slide (duration to add to global start) needed to
|
||||
// clear all conflicts, or 0 if the plan is already conflict-free.
|
||||
func plannerAvailabilitySlide(p planner.PlannerITF, priceds map[tools.DataType]map[string]pricing.PricedItemITF, mode booking.BookingMode) (time.Duration, error) {
|
||||
maxSlide := time.Duration(0)
|
||||
for _, dt := range []tools.DataType{tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE} {
|
||||
for _, priced := range priceds[dt] {
|
||||
locStart := priced.GetLocationStart()
|
||||
locEnd := priced.GetLocationEnd()
|
||||
if locStart == nil || locEnd == nil {
|
||||
continue // open-ended: skip availability check
|
||||
}
|
||||
d := locEnd.Sub(*locStart)
|
||||
next := p.NextAvailableStart(priced.GetID(), priced.GetInstanceID(), *locStart, d)
|
||||
slide := next.Sub(*locStart)
|
||||
if slide <= 0 {
|
||||
continue
|
||||
}
|
||||
if mode == booking.PLANNED {
|
||||
return 0, errors.New("requested slot is not available for resource " + priced.GetID())
|
||||
}
|
||||
if slide > maxSlide {
|
||||
maxSlide = slide
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxSlide, nil
|
||||
}
|
||||
|
||||
// Returns a map of DataType (processing,computing,data,storage,worfklow) where each resource (identified by its UUID)
|
||||
// is mapped to the list of its items (different appearance) in the graph
|
||||
// ex: if the same Minio storage is represented by several nodes in the graph, in [tools.STORAGE_RESSOURCE] its UUID will be mapped to
|
||||
|
||||
Reference in New Issue
Block a user