Correct
This commit is contained in:
@@ -3,12 +3,10 @@ package booking
|
||||
import (
|
||||
"time"
|
||||
|
||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/enum"
|
||||
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -68,40 +66,15 @@ func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResu
|
||||
return m
|
||||
}
|
||||
|
||||
// CheckBooking checks if a booking is possible on a specific compute resource
|
||||
func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) {
|
||||
// check if
|
||||
if end == nil {
|
||||
// if no end... then Book like a savage
|
||||
e := start.Add(5 * time.Minute)
|
||||
end = &e
|
||||
}
|
||||
accessor := NewAccessor(nil)
|
||||
res, code, err := accessor.Search(&dbs.Filters{
|
||||
And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date
|
||||
"resource_id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
||||
"state": {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
|
||||
"expected_start_date": {
|
||||
{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)},
|
||||
{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)},
|
||||
},
|
||||
},
|
||||
}, "", wfa.IsDraft)
|
||||
if code != 200 {
|
||||
return false, err
|
||||
}
|
||||
return len(res) <= parrallelAllowed, nil
|
||||
}
|
||||
|
||||
func (d *Booking) GetDelayForLaunch() time.Duration {
|
||||
return d.RealStartDate.Sub(d.ExpectedStartDate)
|
||||
}
|
||||
|
||||
func (d *Booking) GetDelayForFinishing() time.Duration {
|
||||
if d.ExpectedEndDate == nil {
|
||||
if d.ExpectedEndDate == nil || d.RealEndDate == nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
return d.RealEndDate.Sub(d.ExpectedStartDate)
|
||||
return d.RealEndDate.Sub(*d.ExpectedEndDate)
|
||||
}
|
||||
|
||||
func (d *Booking) GetUsualDuration() time.Duration {
|
||||
@@ -133,14 +106,20 @@ func (r *Booking) StoreDraftDefault() {
|
||||
}
|
||||
|
||||
func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
|
||||
if !r.IsDraft && r.State != set.(*Booking).State || r.RealStartDate != set.(*Booking).RealStartDate || r.RealEndDate != set.(*Booking).RealEndDate {
|
||||
return true, &Booking{
|
||||
State: set.(*Booking).State,
|
||||
RealStartDate: set.(*Booking).RealStartDate,
|
||||
RealEndDate: set.(*Booking).RealEndDate,
|
||||
} // only state can be updated
|
||||
incoming := set.(*Booking)
|
||||
if !r.IsDraft && r.State != incoming.State || r.RealStartDate != incoming.RealStartDate || r.RealEndDate != incoming.RealEndDate {
|
||||
patch := &Booking{
|
||||
State: incoming.State,
|
||||
RealStartDate: incoming.RealStartDate,
|
||||
RealEndDate: incoming.RealEndDate,
|
||||
}
|
||||
// Auto-set RealStartDate when transitioning to STARTED and not already set
|
||||
if r.State != enum.STARTED && incoming.State == enum.STARTED && patch.RealStartDate == nil {
|
||||
now := time.Now()
|
||||
patch.RealStartDate = &now
|
||||
}
|
||||
return true, patch
|
||||
}
|
||||
// TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner
|
||||
return r.IsDraft, set
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user