This commit is contained in:
mr
2026-03-20 16:14:07 +01:00
parent 9f861e5b8d
commit 88d2e52628
5 changed files with 149 additions and 61 deletions

View File

@@ -2,7 +2,7 @@ package planner
import (
"encoding/json"
"fmt"
"sort"
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
@@ -40,6 +40,12 @@ type PlannerSlot struct {
Usage map[string]float64 `json:"usage,omitempty"` // dimension -> % of max (0-100)
}
// PlannerITF is the interface used by Planify to check resource availability.
// *Planner satisfies this interface.
type PlannerITF interface {
NextAvailableStart(resourceID, instanceID string, start time.Time, d time.Duration) time.Time
}
// Planner is a volatile (non-persisted) object that organises bookings by resource.
// Only ComputeResource and StorageResource bookings appear in the schedule.
type Planner struct {
@@ -79,7 +85,6 @@ func generate(request *tools.APIRequest, shallow bool) (*Planner, error) {
}, "*", true)
bookings := append(confirmed, drafts...)
fmt.Println("BOOKS", len(bookings))
p := &Planner{
GeneratedAt: time.Now(),
Schedule: map[string][]*PlannerSlot{},
@@ -97,7 +102,6 @@ func generate(request *tools.APIRequest, shallow bool) (*Planner, error) {
// Only compute and storage resources are eligible
if bk.ResourceType != tools.COMPUTE_RESOURCE && bk.ResourceType != tools.STORAGE_RESOURCE {
fmt.Println("Not eligible")
continue
}
@@ -157,30 +161,23 @@ func (p *Planner) Check(resourceID string, instanceID string, req *ResourceReque
slots, ok := p.Schedule[resourceID]
if !ok {
fmt.Println("CHECK1", true)
return true
}
fmt.Println("CHECK2", len(slots))
for _, slot := range slots {
// Only consider slots on the same instance
if slot.InstanceID != instanceID {
fmt.Println("CHECK3 MISS", slot.InstanceID, instanceID)
continue
}
// Only consider overlapping slots
if !slot.Start.Before(*end) || !slot.End.After(start) {
fmt.Println("CHECK4 MISS", slot.Start, slot.End, start, end)
continue
}
fmt.Println("CHECK5", reqPct)
// If capacity is unknown (reqPct empty), any overlap blocks the slot.
if len(reqPct) == 0 {
return false
}
// Combined usage must not exceed 100 % for any requested dimension
for dim, needed := range reqPct {
fmt.Println("CHECK6", slot.Usage[dim]+needed)
if slot.Usage[dim]+needed >= 100.0 {
return false
}
@@ -287,7 +284,6 @@ func extractSlotData(bk *booking.Booking, request *tools.APIRequest) (instanceID
if err != nil {
return
}
fmt.Println("EXTRACT CLOT", bk.ResourceType)
switch bk.ResourceType {
case tools.COMPUTE_RESOURCE:
instanceID, usage, cap = extractComputeSlot(b, bk.ResourceID, request)
@@ -304,20 +300,17 @@ func extractComputeSlot(pricedJSON []byte, resourceID string, request *tools.API
var priced resources.PricedComputeResource
if err := json.Unmarshal(pricedJSON, &priced); err != nil {
fmt.Println("extractComputeSlot", err)
return
}
res, _, err := (&resources.ComputeResource{}).GetAccessor(request).LoadOne(resourceID)
if err != nil {
fmt.Println("extractComputeSlot2", err)
return
}
compute := res.(*resources.ComputeResource)
instance := findComputeInstance(compute, priced.InstancesRefs)
if instance == nil {
fmt.Println("extractComputeSlot no instance found", err)
return
}
instanceID = instance.GetID()
@@ -472,3 +465,34 @@ func totalRAM(instance *resources.ComputeResourceInstance) float64 {
}
return total
}
// NextAvailableStart returns the earliest time >= start when resourceID/instanceID has a
// free window of duration d. Slots are scanned in order so a single linear pass suffices.
// If the planner has no slots for this resource/instance, start is returned unchanged.
func (p *Planner) NextAvailableStart(resourceID, instanceID string, start time.Time, d time.Duration) time.Time {
slots := p.Schedule[resourceID]
if len(slots) == 0 {
return start
}
// Collect and sort slots for this instance by start time.
relevant := make([]*PlannerSlot, 0, len(slots))
for _, s := range slots {
if s.InstanceID == instanceID {
relevant = append(relevant, s)
}
}
sort.Slice(relevant, func(i, j int) bool { return relevant[i].Start.Before(relevant[j].Start) })
end := start.Add(d)
for _, slot := range relevant {
if !slot.Start.Before(end) {
break // all remaining slots start after our window — done
}
if slot.End.After(start) {
// conflict: push start to after this slot
start = slot.End
end = start.Add(d)
}
}
return start
}