oc-lib/models/workflow/workflow.go
2025-01-15 10:56:44 +01:00

200 lines
8.3 KiB
Go

package workflow
import (
"errors"
"time"
"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/pricing"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
"cloud.o-forge.io/core/oc-lib/tools"
)
/*
* Workflow is a struct that represents a workflow
* it defines the native workflow
*/
type Workflow struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
resources.ResourceSet
Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
// Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow // AbstractWorkflow contains the basic fields of a workflow
}
func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}
func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) {
for _, item := range w.Graph.Items {
if f(item) {
list_datas = append(list_datas, item)
}
}
return
}
func (w *Workflow) GetPricedItem(f func(item graph.GraphItem) bool, request *tools.APIRequest) map[string]pricing.PricedItemITF {
list_datas := map[string]pricing.PricedItemITF{}
for _, item := range w.Graph.Items {
if f(item) {
dt, res := item.GetResource()
ord := res.ConvertToPricedResource(dt, request)
list_datas[res.GetID()] = ord
}
}
return list_datas
}
func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) []resources.ResourceInterface {
storages := []resources.ResourceInterface{}
for _, link := range w.Graph.Links {
nodeID := link.Destination.ID
var node resources.ResourceInterface
if g(w.Graph.Items[link.Source.ID]) {
item := w.Graph.Items[link.Source.ID]
_, node = item.GetResource()
}
if node == nil && g(w.Graph.Items[link.Destination.ID]) { // if the source is not a storage, we consider that the destination is the storage
nodeID = link.Source.ID
item := w.Graph.Items[link.Destination.ID] // and the processing is the source
_, node = item.GetResource() // we are looking for the storage as destination
}
if processingID == nodeID && node != nil { // if the storage is linked to the processing
storages = append(storages, node)
}
}
return storages
}
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
isAuthorized := false
if len(ao.Shared) > 0 {
for _, shared := range ao.Shared {
shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(shared)
if code != 200 || shared == nil {
isAuthorized = false
}
isAuthorized = shared.VerifyAuth(request)
}
}
return ao.AbstractObject.VerifyAuth(request) || isAuthorized
}
/*
* CheckBooking is a function that checks the booking of the workflow on peers (even ourselves)
*/
func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
// check if
if wfa.Graph == nil { // no graph no booking
return false, nil
}
accessor := (&resources.ComputeResource{}).GetAccessor(&tools.APIRequest{Caller: caller})
for _, link := range wfa.Graph.Links {
if ok, compute_id := link.IsComputeLink(*wfa.Graph); ok { // check if the link is a link between a compute and a resource
compute, code, _ := accessor.LoadOne(compute_id)
if code != 200 {
continue
}
// CHECK BOOKING ON PEER, compute could be a remote one
peerID := compute.(*resources.ComputeResource).CreatorID
if peerID == "" {
return false, errors.New("no peer id")
} // no peer id no booking, we need to know where to book
_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, compute_id, tools.BOOKING, tools.GET, nil, caller)
if err != nil {
return false, err
}
}
}
return true, nil
}
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType][]pricing.PricedItemITF, *Workflow, error) {
priceds := map[tools.DataType][]pricing.PricedItemITF{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
}, func(started time.Time, duration float64) time.Time {
return started.Add(time.Duration(duration))
})
if err != nil {
return 0, priceds, nil, err
}
if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, wf.Graph.IsData,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start, 0
}, func(started time.Time, duration float64) time.Time {
return *end
}); err != nil {
return 0, priceds, nil, err
}
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, wf, priceds, request, f,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
if f(i) {
_, r = i.GetResource()
}
return r
}, request)
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
}, func(started time.Time, duration float64) time.Time {
return started.Add(time.Duration(duration))
}); err != nil {
return 0, priceds, nil, err
}
}
longest := common.GetPlannerLongestTime(end, priceds, request)
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
longest := float64(-1)
r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
if code != 200 || err != nil {
return start, longest
}
if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
return start, longest
} else if neoLongest > longest {
longest = neoLongest
}
return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest
}, func(start time.Time, longest float64) time.Time {
return start.Add(time.Duration(longest) * time.Second)
}); err != nil {
return 0, priceds, nil, err
}
return longest, priceds, wf, nil
}
func plan[T resources.ResourceInterface](dt tools.DataType, wf *Workflow, priceds map[tools.DataType][]pricing.PricedItemITF, request *tools.APIRequest,
f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) time.Time) ([]T, map[tools.DataType][]pricing.PricedItemITF, error) {
resources := []T{}
for _, item := range wf.GetGraphItems(f) {
if priceds[dt] == nil {
priceds[dt] = []pricing.PricedItemITF{}
}
dt, realItem := item.GetResource()
if realItem == nil {
return resources, priceds, errors.New("could not load the processing resource")
}
priced := realItem.ConvertToPricedResource(dt, request)
started, duration := start(realItem, priced)
priced.SetLocationStart(started)
if duration >= 0 {
priced.SetLocationEnd(end(started, duration))
}
priced.SetLocationEnd(end(started, priced.GetExplicitDurationInS()))
resources = append(resources, realItem.(T))
priceds[dt] = append(priceds[dt], priced)
}
return resources, priceds, nil
}