Compare commits

...

3 Commits

Author SHA1 Message Date
mr
be3b09b683 set up unfonctionnal rework, TODO -> pricing separation 2024-12-17 10:42:00 +01:00
mr
7696f065f8 modelling 2024-12-16 12:17:20 +01:00
mr
02d1e93c78 massive draft for payment process (UNCOMPLETE) 2024-12-12 16:25:47 +01:00
59 changed files with 3361 additions and 1186 deletions

View File

@ -7,7 +7,7 @@ abstract Resource{
+icon: string +icon: string
+description: string +description: string
+graphic: GraphicElement +graphic: GraphicElement
+element: DataResource/ProcessingResource/StorageResource/Workflow/DatacenterResource +element: DataResource/ProcessingResource/StorageResource/Workflow/ComputeResource
} }
class DataResource { class DataResource {
@ -31,7 +31,7 @@ class StorageResource {
+capacity: int +capacity: int
} }
class DatacenterResource { class ComputeResource {
+UUID: int +UUID: int
+name: string +name: string
@ -96,7 +96,7 @@ class UserWorkflows {
class DatacenterWorkflows { class DatacenterWorkflows {
+UUID: int +UUID: int
+datacenter: DatacenterResource +compute: ComputeResource
+workflows: Workflow[] +workflows: Workflow[]
} }
@ -159,7 +159,7 @@ DatacenterWorkflows "1" o-- "0..*" Workflow
Resource<|-- DataResource Resource<|-- DataResource
Resource<|-- ProcessingResource Resource<|-- ProcessingResource
Resource<|-- StorageResource Resource<|-- StorageResource
Resource<|-- DatacenterResource Resource<|-- ComputeResource
Resource<|-- Workflow Resource<|-- Workflow
ResourceSet "1" o-- "0..*" Ressource ResourceSet "1" o-- "0..*" Ressource

325
doc/order_model.puml Normal file
View File

@ -0,0 +1,325 @@
@startuml
class AbstractObject {
ID string
Name string
IsDraft bool // is consider as a draft
UpdateDate date
LastPeerWriter string
CreatorID string
AccessMode int // public or private
}
AbstractObject ^-- AbstractResource
AbstractObject ^-- Order
AbstractObject ^-- Booking
AbstractObject ^-- BuyingStatus
AbstractObject ^-- WorkflowExecution
AbstractObject ^-- Workflow
class AbstractResource {
Logo string
Description string
ShortDescription string
Owners []string
UsageRestrictions string
VerifyAuth(request) bool
}
AbstractResource "1 " --* "many " InstanceITF
AbstractCustomizedResource "1 " --* "1 " InstanceITF
AbstractResource ^-- ComputeResource
AbstractResource ^-- DataResource
AbstractResource ^-- ProcessingResource
AbstractResource ^-- StorageResource
AbstractResource ^-- WorkflowResource
class ComputeResource {
Architecture string
Infrastructure string
}
class DataResource {
Type string
Quality string
OpenData bool
Static bool
UpdatePeriod date
PersonalData bool
AnonymizedPersonalData bool
SizeGB float64
Licence string
Example string
}
ProcessingResource "1 " *-- "1 " ProcessingUsage
class ProcessingUsage {
CPUs map[string]CPU
GPUs map[string]GPU
RAM RAM
StorageGB float64
Hypothesis string
ScalingModel string
}
class ProcessingResource {
Infrastructure string
Service bool
Usage ProcessingUsage
OpenSource bool
License string
Maturity string
}
class StorageResource {
Type string
Accronym string
}
WorkflowResource "1 " --* "many " ComputeResource
WorkflowResource "1 " --* "many " DataResource
WorkflowResource "1 " --* "many " ProcessingResource
WorkflowResource "1 " --* "many " StorageResource
class WorkflowResource {
WorkflowID string
}
class ExploitResourceSet {}
AbstractCustomizedResource --^ AbstractResource
AbstractCustomizedResource --* ExploitResourceSet
ExploitResourceSet ^-- CustomizedComputeResource
ExploitResourceSet ^-- CustomizedDataResource
ExploitResourceSet ^-- CustomizedProcessingResource
ExploitResourceSet ^-- CustomizedStorageResource
ExploitResourceSet ^-- CustomizedWorkflowResource
class AbstractCustomizedResource {
// A customized resource is an
// extended abstract resource not use in catalog
ExplicitBookingDurationS float64
UsageStart date
UsageEnd date
SelectedPricing string
}
class CustomizedComputeResource {
CPUsLocated map[string]float64
GPUsLocated map[string]float64
RAMLocated float64
}
class CustomizedDataResource {
StorageGB float64
}
class CustomizedProcessingResource {
Container Container
}
class CustomizedStorageResource {
StorageGB bool
}
class CustomizedWorkflowResource {}
interface InstanceITF {
GetID() string
VerifyPartnership() bool // eval if there is one partnership per peer groups in every instance
GetPeerGroups() []ResourcePartnerITF, []map[string][]string
ClearPeerGroups()
}
InstanceITF -- ResourceInstance
ResourceInstance ^-- ComputeResourceInstance
ResourceInstance ^-- StorageResourceInstance
ResourceInstance "many " --* "1 " ResourcePartnerITF
class ResourceInstance {
ID string
Location Geopoint
Country CountryCode
AccessProtocol string
}
class ComputeResourceInstance {
SecurityLevel string
PowerSource string
CPUs map[string]CPU
GPUs map[string]GPU
RAM RAM
}
class StorageResourceInstance {
Local bool
SecurityLevel string
SizeType string
SizeGB int
Encryption bool
Redundancy string
Throughput string
}
ResourcePartnerITF -- ResourcePartnership
ResourcePartnership ^-- ComputeResourcePartnership
ResourcePartnership ^-- DataResourcePartnership
ResourcePartnership ^-- StorageResourcePartnership
interface ResourcePartnerITF {
GetPricing(id string) PricingProfileITF
GetPeerGroups() []ResourcePartnerITF, []map[string][]string
ClearPeerGroups()
}
ResourcePartnership "many " --* "1 " PricingProfileITF
class ResourcePartnership{
Namespace string
PeerGroups map[string][]string
}
class ComputeResourcePartnership {
MaxAllowedCPUsCores map[string]int
MaxAllowedGPUsMemoryGB map[string]float64
RAMSizeGB float64
}
class DataResourcePartnership {
MaxDownloadableGBAllowed float64
PersonalDataAllowed bool
AnonymizedPersonalDataAllowed bool
}
class StorageResourcePartnership {
MaxSizeGBAllowed float64
OnlyEncryptedAllowed bool
}
RefundType -- AccessPricingProfile
enum RefundType {
REFUND_DEAD_END
REFUND_ON_ERROR
REFUND_ON_EARLY_END
}
PricingProfileITF -- AccessPricingProfile
PricingProfileITF -- ExploitPricingProfile
PricingProfileITF -- WorkflowResourcePricingProfile
AccessPricingProfile ^-- DataResourcePricingProfile
AccessPricingProfile ^-- ProcessingResourcePricingProfile
ExploitPricingProfile ^-- ComputeResourcePricingProfile
ExploitPricingProfile ^-- StorageResourcePricingProfile
interface PricingProfileITF {
GetPrice(quantity float64, val float64, start date, end date, request) float64
IsPurchased() bool
}
class AccessPricingProfile {
ID string
Pricing PricingStrategy
DefaultRefundType RefundType
RefundRatio int // percentage of refund on price
}
class DataResourcePricingProfile {}
class ProcessingResourcePricingProfile {}
ExploitPrivilegeStrategy -- ExploitPricingProfile
enum ExploitPrivilegeStrategy {
BASIC
GARANTED_ON_DELAY
GARANTED
}
AccessPricingProfile --* PricingStrategy
AccessPricingProfile ^-- ExploitPricingProfile
class ExploitPricingProfile {
AdditionnalRefundTypes RefundTypeint
PrivilegeStrategy ExploitPrivilegeStrategy
GarantedDelaySecond int
Exceeding bool
ExceedingRatio int // percentage of Exceeding based on price
}
class ComputeResourcePricingProfile {
OverrideCPUsPrices map[string]float64
OverrideGPUsPrices map[string]float64
OverrideRAMPrice float64
}
class StorageResourcePricingProfile {}
WorkflowResourcePricingProfile "1 " --* "many " ExploitResourceSet
class WorkflowResourcePricingProfile {
ID string
}
BuyingStrategy -- PricingStrategy
enum BuyingStrategy {
UNLIMITED
SUBSCRIPTION
PAY_PER_USE
}
Strategy -- TimePricingStrategy
Strategy "0-1 " *-- " " PricingStrategy
interface Strategy {
GetStrategy () string
GetStrategyValue() int
}
enum TimePricingStrategy {
ONCE
PER_SECOND
PER_MINUTE
PER_HOUR
PER_DAY
PER_WEEK
PER_MONTH
}
class PricingStrategy {
Price float64
BuyingStrategy
TimePricingStrategy TimePricingStrategy
OverrideStrategy Strategy
}
PeerOrder "many " *-- "1 " Order
PeerItemOrder "many " *-- "1 " PeerOrder
PricedItemITF "many " *-- "1 " PeerItemOrder
PricedItemITF -- AbstractCustomizedResource
class Order {
OrderBy string
WorkflowExecutionIDs []string
Status string
Total float64
}
class PeerOrder {
PeerID string
Error string
Status string
BillingAddress string
Total float64
}
class PeerItemOrder {
Quantity int
BuyingStatus string
}
class BuyingStatus {}
WorkflowExecution "many " --* "1 " Workflow
Workflow "1 " --* "many " WorkflowScheduler
WorkflowScheduler "1 " --* "many " WorkflowExecution
class WorkflowExecution {
ExecDate date
EndDate date
State string
WorkflowID string
ToBookings() []Booking
}
class WorkflowScheduler* {
Message string
Warning string
Start date
End date
DurationS float64
Cron string
Schedules(workflowID string, request) []WorkflowExecution
}
Workflow "1 " --* "many " ExploitResourceSet
class Workflow {}
interface PricedItemITF {
getPrice(request) float64, error
}
@enduml

29
doc/paymentflowV1.puml Normal file
View File

@ -0,0 +1,29 @@
@startuml
user -> client : schedule
client -> OrderAPIP1 : check book
OrderAPIP1 -> datacenterAPIP2 : check book
datacenterAPIP2 -> OrderAPIP1 : send ok
OrderAPIP1 -> datacenterAPIP2 : generate draft book
OrderAPIP1 -> client : send ok
client -> OrderAPIP1 : send scheduler
OrderAPIP1 -> OrderAPIP1 : draft executions
OrderAPIP1 -> OrderAPIP1 : draft order
OrderAPIP1 -> client : send drafted order
client -> user :
user -> client : select pricing profile
client -> OrderAPIP1 : update order
OrderAPIP1 -> datacenterAPIP2 : check book
datacenterAPIP2 -> OrderAPIP1 : send ok
OrderAPIP1 -> datacenterAPIP2 : generate draft book
OrderAPIP1 -> client : send order
user -> client : order
client -> OrderAPIP1 : order
OrderAPIP1 -> PaymentAPIBCP1 : send payment
PaymentAPIBCP1 -> OrderAPIP1 : send ok
OrderAPIP1 -> datacenterAPIP2 : undraft booking
OrderAPIP1 -> OrderAPIP1 : undraft execution
OrderAPIP1 -> OrderAPIP1 : undraft order
OrderAPIP1 -> client : send ok
client -> client : redirect
@enduml

View File

@ -17,6 +17,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models" "cloud.o-forge.io/core/oc-lib/models"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
"cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/peer" "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/resources"
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "cloud.o-forge.io/core/oc-lib/models/resources/resource_model"
@ -50,6 +51,7 @@ const (
COLLABORATIVE_AREA = tools.COLLABORATIVE_AREA COLLABORATIVE_AREA = tools.COLLABORATIVE_AREA
RULE = tools.RULE RULE = tools.RULE
BOOKING = tools.BOOKING BOOKING = tools.BOOKING
ORDER = tools.ORDER
) )
// will turn into standards api hostnames // will turn into standards api hostnames
@ -200,13 +202,12 @@ func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string,
If not we will store it If not we will store it
Resource model is the model that will define the structure of the resources Resource model is the model that will define the structure of the resources
*/ */
accessor := (&resource_model.ResourceModel{}).GetAccessor("", "", []string{}, nil) accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
for _, model := range []string{tools.DATA_RESOURCE.String(), tools.PROCESSING_RESOURCE.String(), tools.STORAGE_RESOURCE.String(), tools.COMPUTE_RESOURCE.String(), tools.WORKFLOW_RESOURCE.String()} { for _, model := range []string{tools.DATA_RESOURCE.String(), tools.PROCESSING_RESOURCE.String(), tools.STORAGE_RESOURCE.String(), tools.COMPUTE_RESOURCE.String(), tools.WORKFLOW_RESOURCE.String()} {
data, code, _ := accessor.Search(nil, model) data, code, _ := accessor.Search(nil, model, true)
if code == 404 || len(data) == 0 { if code == 404 || len(data) == 0 {
refs := map[string]string{} refs := map[string]string{}
m := map[string]resource_model.Model{} m := map[string]resource_model.Model{}
// TODO Specify the model for each resource
// for now only processing is specified here (not an elegant way) // for now only processing is specified here (not an elegant way)
if model == tools.DATA_RESOURCE.String() || model == tools.STORAGE_RESOURCE.String() { if model == tools.DATA_RESOURCE.String() || model == tools.STORAGE_RESOURCE.String() {
refs["path"] = "string" refs["path"] = "string"
@ -279,6 +280,59 @@ func NewRequest(collection LibDataEnum, user string, peerID string, groups []str
return &Request{collection: collection, user: user, peerID: peerID, groups: groups, caller: caller} return &Request{collection: collection, user: user, peerID: peerID, groups: groups, caller: caller}
} }
func ToScheduler(m interface{}) (n *workflow_execution.WorkflowSchedule) {
defer func() {
if r := recover(); r != nil {
return
}
}()
return m.(*workflow_execution.WorkflowSchedule)
}
func (r *Request) Schedule(wfID string, start string, end string, durationInS float64, cron string) (*workflow_execution.WorkflowSchedule, error) {
scheduler := workflow_execution.NewScheduler(start, end, durationInS, cron)
if _, _, err := scheduler.Schedules(wfID, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}); err != nil {
return nil, err
}
return scheduler, nil
}
func (r *Request) CheckBooking(wfID string, start string, end string, durationInS float64, cron string) bool {
ok, _, _, err := workflow_execution.NewScheduler(start, end, durationInS, cron).CheckBooking(wfID, r.caller)
if err != nil {
fmt.Println(err)
return false
}
return ok
}
func (r *Request) DraftOrder(scheduler *workflow_execution.WorkflowSchedule) (*order.Order, error) {
o := &order.Order{}
if err := o.DraftOrder(scheduler, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}); err != nil {
return nil, err
}
return o, nil
}
func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error {
return o.Pay(scheduler, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
})
}
/* /*
* Search will search for the data in the database * Search will search for the data in the database
* @param filters *dbs.Filters * @param filters *dbs.Filters
@ -287,14 +341,19 @@ func NewRequest(collection LibDataEnum, user string, peerID string, groups []str
* @param c ...*tools.HTTPCaller * @param c ...*tools.HTTPCaller
* @return data LibDataShallow * @return data LibDataShallow
*/ */
func (r *Request) Search(filters *dbs.Filters, word string) (data LibDataShallow) { func (r *Request) Search(filters *dbs.Filters, word string, isDraft bool) (data LibDataShallow) {
defer func() { // recover the panic defer func() { // recover the panic
if r := recover(); r != nil { if r := recover(); r != nil {
tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Search : "+fmt.Sprintf("%v", r))) tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Search : "+fmt.Sprintf("%v", r)))
data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(r.user, r.peerID, r.groups, r.caller).Search(filters, word) d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).Search(filters, word, isDraft)
if err != nil { if err != nil {
data = LibDataShallow{Data: d, Code: code, Err: err.Error()} data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
return return
@ -309,14 +368,19 @@ func (r *Request) Search(filters *dbs.Filters, word string) (data LibDataShallow
* @param c ...*tools.HTTPCaller * @param c ...*tools.HTTPCaller
* @return data LibDataShallow * @return data LibDataShallow
*/ */
func (r *Request) LoadAll() (data LibDataShallow) { func (r *Request) LoadAll(isDraft bool) (data LibDataShallow) {
defer func() { // recover the panic defer func() { // recover the panic
if r := recover(); r != nil { if r := recover(); r != nil {
tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadAll : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack()))) tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadAll : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(r.user, r.peerID, r.groups, r.caller).LoadAll() d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).LoadAll(isDraft)
if err != nil { if err != nil {
data = LibDataShallow{Data: d, Code: code, Err: err.Error()} data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
return return
@ -339,7 +403,12 @@ func (r *Request) LoadOne(id string) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(r.user, r.peerID, r.groups, r.caller).LoadOne(id) d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).LoadOne(id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@ -364,7 +433,12 @@ func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.collection.EnumIndex())
d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).UpdateOne(model.Deserialize(set, model), id) d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).UpdateOne(model.Deserialize(set, model), id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@ -387,7 +461,12 @@ func (r *Request) DeleteOne(id string) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(r.user, r.peerID, r.groups, r.caller).DeleteOne(id) d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).DeleteOne(id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@ -411,7 +490,12 @@ func (r *Request) StoreOne(object map[string]interface{}) (data LibData) {
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.collection.EnumIndex())
d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).StoreOne(model.Deserialize(object, model)) d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).StoreOne(model.Deserialize(object, model))
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@ -435,7 +519,12 @@ func (r *Request) CopyOne(object map[string]interface{}) (data LibData) {
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.collection.EnumIndex())
d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).CopyOne(model.Deserialize(object, model)) d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
}).CopyOne(model.Deserialize(object, model))
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@ -447,73 +536,80 @@ func (r *Request) CopyOne(object map[string]interface{}) (data LibData) {
// ================ CAST ========================= // // ================ CAST ========================= //
func (l *LibData) ToDataResource() *resources.DataResource { func (l *LibData) ToDataResource() *resources.DataResource {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.DATA_RESOURCE { if l.Data.GetAccessor(nil).GetType() == tools.DATA_RESOURCE {
return l.Data.(*resources.DataResource) return l.Data.(*resources.DataResource)
} }
return nil return nil
} }
func (l *LibData) ToComputeResource() *resources.ComputeResource { func (l *LibData) ToComputeResource() *resources.ComputeResource {
if l.Data != nil && l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COMPUTE_RESOURCE { if l.Data != nil && l.Data.GetAccessor(nil).GetType() == tools.COMPUTE_RESOURCE {
return l.Data.(*resources.ComputeResource) return l.Data.(*resources.ComputeResource)
} }
return nil return nil
} }
func (l *LibData) ToStorageResource() *resources.StorageResource { func (l *LibData) ToStorageResource() *resources.StorageResource {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.STORAGE_RESOURCE { if l.Data.GetAccessor(nil).GetType() == tools.STORAGE_RESOURCE {
return l.Data.(*resources.StorageResource) return l.Data.(*resources.StorageResource)
} }
return nil return nil
} }
func (l *LibData) ToProcessingResource() *resources.ProcessingResource { func (l *LibData) ToProcessingResource() *resources.ProcessingResource {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.PROCESSING_RESOURCE { if l.Data.GetAccessor(nil).GetType() == tools.PROCESSING_RESOURCE {
return l.Data.(*resources.ProcessingResource) return l.Data.(*resources.ProcessingResource)
} }
return nil return nil
} }
func (l *LibData) ToWorkflowResource() *resources.WorkflowResource { func (l *LibData) ToWorkflowResource() *resources.WorkflowResource {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW_RESOURCE { if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_RESOURCE {
return l.Data.(*resources.WorkflowResource) return l.Data.(*resources.WorkflowResource)
} }
return nil return nil
} }
func (l *LibData) ToPeer() *peer.Peer { func (l *LibData) ToPeer() *peer.Peer {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.PEER { if l.Data.GetAccessor(nil).GetType() == tools.PEER {
return l.Data.(*peer.Peer) return l.Data.(*peer.Peer)
} }
return nil return nil
} }
func (l *LibData) ToWorkflow() *w2.Workflow { func (l *LibData) ToWorkflow() *w2.Workflow {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW { if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW {
return l.Data.(*w2.Workflow) return l.Data.(*w2.Workflow)
} }
return nil return nil
} }
func (l *LibData) ToWorkspace() *workspace.Workspace { func (l *LibData) ToWorkspace() *workspace.Workspace {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKSPACE { if l.Data.GetAccessor(nil).GetType() == tools.WORKSPACE {
return l.Data.(*workspace.Workspace) return l.Data.(*workspace.Workspace)
} }
return nil return nil
} }
func (l *LibData) ToCollaborativeArea() *collaborative_area.CollaborativeArea { func (l *LibData) ToCollaborativeArea() *collaborative_area.CollaborativeArea {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COLLABORATIVE_AREA { if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
return l.Data.(*collaborative_area.CollaborativeArea) return l.Data.(*collaborative_area.CollaborativeArea)
} }
return nil return nil
} }
func (l *LibData) ToRule() *rule.Rule { func (l *LibData) ToRule() *rule.Rule {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COLLABORATIVE_AREA { if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
return l.Data.(*rule.Rule) return l.Data.(*rule.Rule)
} }
return nil return nil
} }
func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecution { func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecutions {
if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW_EXECUTION { if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_EXECUTION {
return l.Data.(*workflow_execution.WorkflowExecution) return l.Data.(*workflow_execution.WorkflowExecutions)
}
return nil
}
func (l *LibData) ToOrder() *order.Order {
if l.Data.GetAccessor(nil).GetType() == tools.ORDER {
return l.Data.(*order.Order)
} }
return nil return nil
} }

2
go.mod
View File

@ -27,6 +27,7 @@ require (
require ( require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/biter777/countries v1.7.5
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect
@ -37,6 +38,7 @@ require (
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.1.0 // indirect github.com/kr/text v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect

4
go.sum
View File

@ -3,6 +3,8 @@ github.com/beego/beego/v2 v2.3.1 h1:7MUKMpJYzOXtCUsTEoXOxsDV/UcHw6CPbaWMlthVNsc=
github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4= github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -53,6 +55,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b h1:XBF8THPBy28s2ryI7+/Jf/847unLWxYMpJveX5Kox+0=
github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b/go.mod h1:z1oqhOuuYpPHmUmAK2aNygKFlPdb4o3PppQnVTRFdrI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=

View File

@ -4,8 +4,8 @@ import (
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/common"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
) )
@ -14,49 +14,99 @@ import (
* Booking is a struct that represents a booking * Booking is a struct that represents a booking
*/ */
type Booking struct { type Booking struct {
workflow_execution.WorkflowExecution // WorkflowExecution contains the workflow execution data utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
ComputeResourceID string `json:"compute_resource_id,omitempty" bson:"compute_resource_id,omitempty" validate:"required"` // ComputeResourceID is the ID of the compute resource specified in the booking DestPeerID string `json:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer
ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
State common.ScheduledType `json:"state,omitempty" bson:"state,omitempty" validate:"required"` // State is the state of the booking
ExpectedStartDate time.Time `json:"expected_start_date,omitempty" bson:"expected_start_date,omitempty" validate:"required"` // ExpectedStartDate is the expected start date of the booking
ExpectedEndDate *time.Time `json:"expected_end_date,omitempty" bson:"expected_end_date,omitempty" validate:"required"` // ExpectedEndDate is the expected end date of the booking
RealStartDate *time.Time `json:"real_start_date,omitempty" bson:"real_start_date,omitempty"` // RealStartDate is the real start date of the booking
RealEndDate *time.Time `json:"real_end_date,omitempty" bson:"real_end_date,omitempty"` // RealEndDate is the real end date of the booking
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource
ResourceID string `json:"compute_resource_id,omitempty" bson:"compute_resource_id,omitempty" validate:"required"` // could be a Compute or a Storage
} }
// CheckBooking checks if a booking is possible on a specific compute resource // CheckBooking checks if a booking is possible on a specific compute resource
func (wfa *Booking) CheckBooking(id string, start time.Time, end *time.Time) (bool, error) { func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) {
// check if // check if
if end == nil { if end == nil {
// if no end... then Book like a savage // if no end... then Book like a savage
return true, nil e := start.Add(time.Hour)
end = &e
} }
e := *end accessor := NewAccessor(nil)
accessor := New(tools.BOOKING, "", "", nil, nil)
res, code, err := accessor.Search(&dbs.Filters{ 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 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
"compute_resource_id": {{Operator: dbs.EQUAL.String(), Value: id}}, "resource_id": {{Operator: dbs.EQUAL.String(), Value: id}},
"workflowexecution.state": {{Operator: dbs.EQUAL.String(), Value: workflow_execution.SCHEDULED.EnumIndex()}}, "state": {{Operator: dbs.EQUAL.String(), Value: common.DRAFT.EnumIndex()}},
"workflowexecution.execution_date": { "expected_start_date": {
{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(e)}, {Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)},
{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)}, {Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)},
}, },
}, },
}, "") }, "", wfa.IsDraft)
if code != 200 { if code != 200 {
return false, err return false, err
} }
return len(res) == 0, nil return len(res) <= parrallelAllowed, nil
} }
// tool to convert the argo status to a state func (d *Booking) GetDelayForLaunch() time.Duration {
func (wfa *Booking) ArgoStatusToState(status string) *Booking { return d.RealStartDate.Sub(d.ExpectedStartDate)
wfa.WorkflowExecution.ArgoStatusToState(status) }
return wfa
func (d *Booking) GetDelayForFinishing() time.Duration {
if d.ExpectedEndDate == nil {
return time.Duration(0)
}
return d.RealEndDate.Sub(d.ExpectedStartDate)
}
func (d *Booking) GetUsualDuration() time.Duration {
return d.ExpectedEndDate.Sub(d.ExpectedStartDate)
}
func (d *Booking) GetRealDuration() time.Duration {
if d.RealEndDate == nil || d.RealStartDate == nil {
return time.Duration(0)
}
return d.RealEndDate.Sub(*d.RealStartDate)
}
func (d *Booking) GetDelayOnDuration() time.Duration {
return d.GetRealDuration() - d.GetUsualDuration()
} }
func (d *Booking) GetName() string { func (d *Booking) GetName() string {
return d.UUID + "_" + d.ExecDate.String() return d.GetID() + "_" + d.ExpectedStartDate.String()
} }
func (d *Booking) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.BOOKING, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *Booking) VerifyAuth(username string, peerID string, groups []string) bool { func (d *Booking) VerifyAuth(request *tools.APIRequest) bool {
return true return true
} }
func (r *Booking) StoreDraftDefault() {
r.IsDraft = true
}
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
}
// TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner
return r.IsDraft, set
}
func (r *Booking) CanDelete() bool {
return r.IsDraft // only draft bookings can be deleted
}

View File

@ -5,8 +5,8 @@ import (
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/common"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -15,15 +15,12 @@ type bookingMongoAccessor struct {
} }
// New creates a new instance of the bookingMongoAccessor // New creates a new instance of the bookingMongoAccessor
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *bookingMongoAccessor { func NewAccessor(request *tools.APIRequest) *bookingMongoAccessor {
return &bookingMongoAccessor{ return &bookingMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: tools.BOOKING,
Groups: groups,
User: username, // Set the caller
Type: t,
}, },
} }
} }
@ -49,26 +46,29 @@ func (a *bookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int
func (a *bookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *bookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) {
if d.(*Booking).State == workflow_execution.SCHEDULED && time.Now().UTC().After(*d.(*Booking).ExecDate) { if (d.(*Booking).ExpectedEndDate) == nil {
d.(*Booking).State = workflow_execution.FORGOTTEN d.(*Booking).State = common.FORGOTTEN
utils.GenericRawUpdateOne(d, id, a)
} else if d.(*Booking).State == common.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) {
d.(*Booking).State = common.DELAYED
utils.GenericRawUpdateOne(d, id, a) utils.GenericRawUpdateOne(d, id, a)
} }
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *bookingMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *bookingMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Booking](a.getExec(), a) return utils.GenericLoadAll[*Booking](a.getExec(), isDraft, a)
} }
func (a *bookingMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *bookingMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), a) return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), isDraft, a)
} }
func (a *bookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { func (a *bookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
if d.(*Booking).State == workflow_execution.SCHEDULED && time.Now().UTC().After(*d.(*Booking).ExecDate) { if d.(*Booking).State == common.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) {
d.(*Booking).State = workflow_execution.FORGOTTEN d.(*Booking).State = common.DELAYED
utils.GenericRawUpdateOne(d, d.GetID(), a) utils.GenericRawUpdateOne(d, d.GetID(), a)
} }
return d return d

View File

@ -69,25 +69,25 @@ func (ao *CollaborativeArea) Clear(peerID string) {
ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC() ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC()
} }
func (ao *CollaborativeArea) VerifyAuth(username string, peerID string, groups []string) bool { func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool {
if ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist { if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil {
if grps, ok := ao.AllowedPeersGroup[peerID]; ok || config.GetConfig().Whitelist { if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist {
fmt.Println("grps", grps, "ok", ok, "config.GetConfig().Whitelist", config.GetConfig().Whitelist) fmt.Println("grps", grps, "ok", ok, "config.GetConfig().Whitelist", config.GetConfig().Whitelist)
if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) { if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) {
return true return true
} }
for _, grp := range grps { for _, grp := range grps {
if slices.Contains(groups, grp) { if slices.Contains(request.Groups, grp) {
return true return true
} }
} }
} }
} }
return ao.AbstractObject.VerifyAuth(username, peerID, groups) return ao.AbstractObject.VerifyAuth(request)
} }
func (d *CollaborativeArea) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.COLLABORATIVE_AREA, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *CollaborativeArea) Trim() *CollaborativeArea { func (d *CollaborativeArea) Trim() *CollaborativeArea {

View File

@ -26,19 +26,17 @@ type collaborativeAreaMongoAccessor struct {
ruleAccessor utils.Accessor ruleAccessor utils.Accessor
} }
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *collaborativeAreaMongoAccessor { func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor {
return &collaborativeAreaMongoAccessor{ return &collaborativeAreaMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: tools.COLLABORATIVE_AREA,
Groups: groups, // Set the caller
Type: t,
}, },
workspaceAccessor: (&workspace.Workspace{}).GetAccessor(username, peerID, groups, nil), workspaceAccessor: (&workspace.Workspace{}).GetAccessor(nil),
workflowAccessor: (&w.Workflow{}).GetAccessor(username, peerID, groups, nil), workflowAccessor: (&w.Workflow{}).GetAccessor(nil),
peerAccessor: (&peer.Peer{}).GetAccessor(username, peerID, groups, nil), peerAccessor: (&peer.Peer{}).GetAccessor(nil),
ruleAccessor: (&rule.Rule{}).GetAccessor(username, peerID, groups, nil), ruleAccessor: (&rule.Rule{}).GetAccessor(nil),
} }
} }
@ -69,7 +67,7 @@ func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DB
_, id := (&peer.Peer{}).IsMySelf() // get the local peer _, id := (&peer.Peer{}).IsMySelf() // get the local peer
data.(*CollaborativeArea).Clear(id) // set the creator data.(*CollaborativeArea).Clear(id) // set the creator
// retrieve or proper peer // retrieve or proper peer
dd, code, err := a.peerAccessor.Search(nil, "0") dd, code, err := a.peerAccessor.Search(nil, "0", true)
if code != 200 || len(dd) == 0 { if code != 200 || len(dd) == 0 {
return nil, code, errors.New("Could not retrieve the peer" + err.Error()) return nil, code, errors.New("Could not retrieve the peer" + err.Error())
} }
@ -88,13 +86,13 @@ func (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBO
return a.StoreOne(data) return a.StoreOne(data)
} }
func filterEnrich[T utils.ShallowDBObject](arr []string, a utils.Accessor) []T { func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T {
var new []T var new []T
res, code, _ := a.Search(&dbs.Filters{ res, code, _ := a.Search(&dbs.Filters{
Or: map[string][]dbs.Filter{ Or: map[string][]dbs.Filter{
"abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}}, "abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}},
}, },
}, "") }, "", isDrafted)
if code == 200 { if code == 200 {
for _, r := range res { for _, r := range res {
new = append(new, r.(T)) new = append(new, r.(T))
@ -104,39 +102,39 @@ func filterEnrich[T utils.ShallowDBObject](arr []string, a utils.Accessor) []T {
} }
// enrich is a function that enriches the CollaborativeArea with the shared objects // enrich is a function that enriches the CollaborativeArea with the shared objects
func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea) *CollaborativeArea { func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea, isDrafted bool) *CollaborativeArea {
sharedWorkspace.SharedWorkspaces = append(sharedWorkspace.SharedWorkspaces, sharedWorkspace.SharedWorkspaces = append(sharedWorkspace.SharedWorkspaces,
filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, a.workspaceAccessor)...) filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, isDrafted, a.workspaceAccessor)...)
sharedWorkspace.SharedWorkflows = append(sharedWorkspace.SharedWorkflows, sharedWorkspace.SharedWorkflows = append(sharedWorkspace.SharedWorkflows,
filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, a.workflowAccessor)...) filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, isDrafted, a.workflowAccessor)...)
peerskey := []string{} peerskey := []string{}
for k := range sharedWorkspace.AllowedPeersGroup { for k := range sharedWorkspace.AllowedPeersGroup {
peerskey = append(peerskey, k) peerskey = append(peerskey, k)
} }
sharedWorkspace.SharedPeers = append(sharedWorkspace.SharedPeers, sharedWorkspace.SharedPeers = append(sharedWorkspace.SharedPeers,
filterEnrich[*peer.Peer](peerskey, a.peerAccessor)...) filterEnrich[*peer.Peer](peerskey, isDrafted, a.peerAccessor)...)
sharedWorkspace.SharedRules = append(sharedWorkspace.SharedRules, sharedWorkspace.SharedRules = append(sharedWorkspace.SharedRules,
filterEnrich[*rule.Rule](sharedWorkspace.Rules, a.ruleAccessor)...) filterEnrich[*rule.Rule](sharedWorkspace.Rules, isDrafted, a.ruleAccessor)...)
return sharedWorkspace return sharedWorkspace
} }
func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return a.enrich(d.(*CollaborativeArea)), 200, nil return a.enrich(d.(*CollaborativeArea), true), 200, nil
}, a) }, a)
} }
func (a *collaborativeAreaMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
return a.enrich(d.(*CollaborativeArea)) return a.enrich(d.(*CollaborativeArea), true)
}, a) }, isDrafted, a)
} }
func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search), return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search),
func(d utils.DBObject) utils.ShallowDBObject { func(d utils.DBObject) utils.ShallowDBObject {
return a.enrich(d.(*CollaborativeArea)) return a.enrich(d.(*CollaborativeArea), true)
}, a) }, isDrafted, a)
} }
/* /*
@ -149,12 +147,12 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr
if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string
for _, v := range eld.Workspaces { for _, v := range eld.Workspaces {
a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v) a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v)
if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKSPACE] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
continue continue
} }
paccess := (&peer.Peer{}) // send to all peers paccess := (&peer.Peer{}) // send to all peers
for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer
b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.Caller) b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.GetCaller())
if err != nil && b == nil { if err != nil && b == nil {
a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
} }
@ -165,7 +163,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr
if shared.Workspaces != nil { if shared.Workspaces != nil {
for _, v := range shared.Workspaces { // update all the collaborative areas for _, v := range shared.Workspaces { // update all the collaborative areas
workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace
if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKSPACE] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
continue continue
} }
for k := range shared.AllowedPeersGroup { for k := range shared.AllowedPeersGroup {
@ -175,7 +173,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr
paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer
s := workspace.Serialize(workspace) s := workspace.Serialize(workspace)
s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.Caller) b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.GetCaller())
if err != nil && b == nil { if err != nil && b == nil {
a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
} }
@ -205,12 +203,12 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
n := &w.Workflow{} n := &w.Workflow{}
n.Shared = new n.Shared = new
a.workflowAccessor.UpdateOne(n, v) a.workflowAccessor.UpdateOne(n, v)
if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKFLOW] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
continue continue
} }
paccess := (&peer.Peer{}) // send to all peers paccess := (&peer.Peer{}) // send to all peers
for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer
b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.Caller) b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.GetCaller())
if err != nil && b == nil { if err != nil && b == nil {
a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
} }
@ -227,7 +225,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
if !slices.Contains(s.Shared, id) { if !slices.Contains(s.Shared, id) {
s.Shared = append(s.Shared, id) s.Shared = append(s.Shared, id)
workflow, code, _ := a.workflowAccessor.UpdateOne(s, v) workflow, code, _ := a.workflowAccessor.UpdateOne(s, v)
if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKFLOW] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
continue continue
} }
paccess := (&peer.Peer{}) paccess := (&peer.Peer{})
@ -235,7 +233,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
if code == 200 { if code == 200 {
s := workflow.Serialize(workflow) // add the shared workflow on the peer s := workflow.Serialize(workflow) // add the shared workflow on the peer
s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.Caller) b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.GetCaller())
if err != nil && b == nil { if err != nil && b == nil {
a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
} }
@ -260,7 +258,7 @@ func (a *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) {
} }
func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) { func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) {
if a.Caller == nil || a.Caller.URLS == nil || a.Caller.URLS[tools.COLLABORATIVE_AREA] == nil || a.Caller.Disabled { if a.GetCaller() == nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.COLLABORATIVE_AREA] == nil || a.GetCaller().Disabled {
return return
} }
@ -270,7 +268,7 @@ func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea,
continue continue
} }
shared.IsSent = meth == tools.POST shared.IsSent = meth == tools.POST
b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.Caller) b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.GetCaller())
if err != nil && b == nil { if err != nil && b == nil {
a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
} }

View File

@ -20,10 +20,10 @@ func (r *Rule) GenerateID() {
r.UUID = uuid.New().String() r.UUID = uuid.New().String()
} }
func (d *Rule) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.RULE, username, peerID, groups, caller) return NewAccessor(request)
} }
func (d *Rule) VerifyAuth(username string, peerID string, groups []string) bool { func (d *Rule) VerifyAuth(request *tools.APIRequest) bool {
return true return true
} }

View File

@ -2,7 +2,6 @@ package rule
import ( import (
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
@ -13,89 +12,51 @@ type ruleMongoAccessor struct {
} }
// New creates a new instance of the ruleMongoAccessor // New creates a new instance of the ruleMongoAccessor
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *ruleMongoAccessor { func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor {
return &ruleMongoAccessor{ return &ruleMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: tools.RULE,
Groups: groups, // Set the caller
User: username,
Type: t,
}, },
} }
} }
// GetType returns the type of the rule /*
* Nothing special here, just the basic CRUD operations
*/
func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a) return utils.GenericDeleteOne(id, a)
} }
// UpdateOne updates a rule in the database
func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set.(*Rule), id, a, &Rule{}) return utils.GenericUpdateOne(set, id, a, &Rule{})
} }
// StoreOne stores a rule in the database
func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Rule), a) return utils.GenericStoreOne(data, a)
} }
func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a) return utils.GenericStoreOne(data, a)
} }
// LoadOne loads a rule from the database
func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
var rule Rule return utils.GenericLoadOne[*Rule](id, func(d utils.DBObject) (utils.DBObject, int, error) {
res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String()) return d, 200, nil
if err != nil { }, a)
a.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
return nil, code, err
}
res_mongo.Decode(&rule)
return &rule, 200, nil
} }
// LoadAll loads all rules from the database func (a *ruleMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
func (a ruleMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*Rule](a.getExec(), isDraft, a)
objs := []utils.ShallowDBObject{}
res_mongo, code, err := mongo.MONGOService.LoadAll(a.GetType().String())
if err != nil {
a.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
return nil, code, err
}
var results []Rule
if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
return nil, 404, err
}
for _, r := range results {
objs = append(objs, &r)
}
return objs, 200, nil
} }
// Search searches for rules in the database, given some filters OR a search string func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*Rule](filters, search, (&Rule{}).GetObjectFilters(search), a.getExec(), isDraft, a)
objs := []utils.ShallowDBObject{} }
if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
filters = &dbs.Filters{ func (a *ruleMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided return func(d utils.DBObject) utils.ShallowDBObject {
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, return d
}, }
}
}
res_mongo, code, err := mongo.MONGOService.Search(filters, a.GetType().String())
if err != nil {
a.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
return nil, code, err
}
var results []Rule
if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
return nil, 404, err
}
for _, r := range results {
objs = append(objs, &r)
}
return objs, 200, nil
} }

View File

@ -17,6 +17,6 @@ type ShallowCollaborativeArea struct {
Rules []string `json:"rules,omitempty" bson:"rules,omitempty"` Rules []string `json:"rules,omitempty" bson:"rules,omitempty"`
} }
func (d *ShallowCollaborativeArea) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *ShallowCollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.COLLABORATIVE_AREA, username, peerID, groups, caller) return NewAccessor(request)
} }

View File

@ -11,15 +11,12 @@ type shallowSharedWorkspaceMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor
} }
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *shallowSharedWorkspaceMongoAccessor { func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor {
return &shallowSharedWorkspaceMongoAccessor{ return &shallowSharedWorkspaceMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
Caller: caller, Request: request, // Set the caller
PeerID: peerID, Type: tools.COLLABORATIVE_AREA,
User: username, // Set the caller
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
@ -46,14 +43,14 @@ func (a *shallowSharedWorkspaceMongoAccessor) LoadOne(id string) (utils.DBObject
}, a) }, a)
} }
func (a *shallowSharedWorkspaceMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *shallowSharedWorkspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, a) }, isDraft, a)
} }
func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, a) }, isDraft, a)
} }

View File

@ -0,0 +1,17 @@
package common
type Container struct {
Image string `json:"image,omitempty" bson:"image,omitempty"` // Image is the container image TEMPO
Command string `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command
Args string `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments
Env map[string]string `json:"env,omitempty" bson:"env,omitempty"` // Env is the container environment variables
Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes
Exposes []Expose `bson:"exposes,omitempty" json:"exposes,omitempty"` // Expose is the execution
}
type Expose struct {
Port int `json:"port,omitempty" bson:"port,omitempty"` // Port is the port
Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse
PAT int `json:"pat,omitempty" bson:"pat,omitempty"` // PAT is the PAT
}

33
models/common/devices.go Normal file
View File

@ -0,0 +1,33 @@
package common
// CPU is a struct that represents a CPU
type CPU struct {
Model string `bson:"platform,omitempty" json:"platform,omitempty"`
FrequencyGhz float64 `bson:"frenquency,omitempty" json:"frenquency,omitempty"`
Cores int `bson:"cores,omitempty" json:"cores,omitempty"`
Architecture string `bson:"architecture,omitempty" json:"architecture,omitempty"`
}
type RAM struct {
SizeGb float64 `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"`
Ecc bool `bson:"ecc" json:"ecc" default:"true"`
}
type GPU struct {
Model string `bson:"platform,omitempty" json:"platform,omitempty"`
MemoryGb float64 `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"`
}
type InfrastructureType int
const (
DOCKER InfrastructureType = iota
KUBERNETES
SLURM
HW
CONDOR
)
func (t InfrastructureType) String() string {
return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
}

View File

@ -0,0 +1,17 @@
package pricing
import (
"time"
"cloud.o-forge.io/core/oc-lib/tools"
)
type PricedItemITF interface {
GetID() string
GetType() tools.DataType
IsPurchased(request *tools.APIRequest) bool
GetCreatorID() string
GetLocationStart() *time.Time
GetLocationEnd() *time.Time
GetPrice(request *tools.APIRequest) (float64, error)
}

View File

@ -0,0 +1,60 @@
package pricing
import (
"time"
"cloud.o-forge.io/core/oc-lib/tools"
)
type PricingProfileITF interface {
GetID() string
GetPrice(quantity float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error)
IsPurchased() bool
GetOverrideStrategyValue() int
}
type RefundType int
const (
REFUND_DEAD_END RefundType = iota
REFUND_ON_ERROR
REFUND_ON_EARLY_END
)
type AccessPricingProfile[T Strategy] struct { // only use for acces such as : DATA && PROCESSING
ID string `json:"id,omitempty" bson:"id,omitempty"` // ID is the ID of the pricing
Pricing PricingStrategy[T] `json:"price,omitempty" bson:"price,omitempty"` // Price is the price of the resource
DefaultRefund RefundType `json:"default_refund" bson:"default_refund"` // DefaultRefund is the default refund type of the pricing
RefundRatio int32 `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing
}
func (b *AccessPricingProfile[T]) GetID() string {
return b.ID
}
func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int {
return -1
}
type ExploitPrivilegeStrategy int
const (
BASIC ExploitPrivilegeStrategy = iota
GARANTED_ON_DELAY
GARANTED
)
func (t ExploitPrivilegeStrategy) String() string {
return [...]string{"BASIC", "GARANTED_ON_DELAY", "GARANTED"}[t]
}
type ExploitPricingProfile[T Strategy] struct { // only use for exploit such as : STORAGE, COMPUTE, WORKFLOW
AccessPricingProfile[T]
AdditionnalRefundTypes []RefundType `json:"refund_types" bson:"refund_types"` // RefundTypes is the refund types of the pricing
PrivilegeStrategy ExploitPrivilegeStrategy `json:"privilege_strategy,omitempty" bson:"privilege_strategy,omitempty"` // Strategy is the strategy of the pricing
GarantedDelaySecond uint
Exceeding bool
ExceedingRatio int32 `json:"exceeding_ratio" bson:"exceeding_ratio" default:"0"` // ExceedingRatio is the exceeding ratio of the bill
}

View File

@ -0,0 +1,136 @@
package pricing
import (
"errors"
"fmt"
"strconv"
"time"
)
type BuyingStrategy int
const (
UNLIMITED BuyingStrategy = iota
SUBSCRIPTION
PAY_PER_USE
)
type Strategy interface {
GetStrategy() string
GetStrategyValue() int
}
type TimePricingStrategy int
const (
ONCE TimePricingStrategy = iota
PER_SECOND
PER_MINUTE
PER_HOUR
PER_DAY
PER_WEEK
PER_MONTH
)
func (t TimePricingStrategy) GetStrategy() string {
return [...]string{"ONCE", "PER_SECOND", "PER_MINUTE", "PER_HOUR", "PER_DAY", "PER_WEEK", "PER_MONTH"}[t]
}
func (t TimePricingStrategy) GetStrategyValue() int {
return int(t)
}
func getAverageTimeInSecond(averageTimeInSecond float64, start time.Time, end *time.Time) float64 {
now := time.Now()
after := now.Add(time.Duration(averageTimeInSecond) * time.Second)
fromAverageDuration := after.Sub(now).Seconds()
var tEnd time.Time
if end == nil {
tEnd = start.Add(1 * time.Hour)
} else {
tEnd = *end
}
fromDateDuration := tEnd.Sub(start).Seconds()
if fromAverageDuration > fromDateDuration {
return fromAverageDuration
}
return fromDateDuration
}
func BookingEstimation(t TimePricingStrategy, price float64, locationDurationInSecond float64, start time.Time, end *time.Time) (float64, error) {
locationDurationInSecond = getAverageTimeInSecond(locationDurationInSecond, start, end)
priceStr := fmt.Sprintf("%v", price)
p, err := strconv.ParseFloat(priceStr, 64)
if err != nil {
return 0, err
}
switch t {
case ONCE:
return p, nil
case PER_HOUR:
return p * float64(locationDurationInSecond/3600), nil
case PER_MINUTE:
return p * float64(locationDurationInSecond/60), nil
case PER_SECOND:
return p * locationDurationInSecond, nil
case PER_DAY:
return p * float64(locationDurationInSecond/86400), nil
case PER_WEEK:
return p * float64(locationDurationInSecond/604800), nil
case PER_MONTH:
return p * float64(locationDurationInSecond/2592000), nil
}
return 0, errors.New("Pricing strategy not found")
}
// hmmmm
type PricingStrategy[T Strategy] struct {
Price float64 `json:"Price" bson:"Price" default:"0"` // Price is the Price of the pricing
BuyingStrategy BuyingStrategy `json:"buying_strategy" bson:"buying_strategy" default:"0"` // BuyingStrategy is the buying strategy of the pricing
TimePricingStrategy TimePricingStrategy `json:"time_pricing_strategy" bson:"time_pricing_strategy" default:"0"` // TimePricingStrategy is the time pricing strategy of the pricing
OverrideStrategy T `json:"override_strategy" bson:"override_strategy" default:"-1"` // Modulation is the modulation of the pricing
}
func (p PricingStrategy[T]) SetStrategy(Price float64, BuyingStrategy BuyingStrategy, TimePricingStrategy TimePricingStrategy) error {
if TimePricingStrategy == ONCE && (BuyingStrategy != UNLIMITED || BuyingStrategy != PAY_PER_USE) {
return errors.New("time pricing strategy can only be set to ONCE if buying strategy is UNLIMITED or PAY_PER_USE")
} else if BuyingStrategy == SUBSCRIPTION && (TimePricingStrategy == ONCE) {
return errors.New("subscription duration in second must be set if buying strategy is SUBSCRIPTION")
}
p.Price = Price
p.BuyingStrategy = BuyingStrategy
p.TimePricingStrategy = TimePricingStrategy
return nil
}
func (p PricingStrategy[T]) SetSpecificPerUseStrategy(strategy T) error {
if p.BuyingStrategy == UNLIMITED {
return errors.New("UNLIMITED buying strategy can't have a specific strategy, Price is set on buying")
}
p.OverrideStrategy = strategy
return nil
}
// QUANTITY can be how many of gb core per example
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) {
if p.BuyingStrategy == SUBSCRIPTION {
return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
} else if p.BuyingStrategy == UNLIMITED {
return p.Price, nil
}
return p.Price * float64(amountOfData), nil
}
func (p PricingStrategy[T]) GetBuyingStrategy() BuyingStrategy {
return p.BuyingStrategy
}
func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
return p.TimePricingStrategy
}
func (p PricingStrategy[T]) GetOverrideStrategy() T {
return p.OverrideStrategy
}

View File

@ -0,0 +1,38 @@
package common
type ScheduledType int
const (
DRAFT ScheduledType = iota
SCHEDULED
STARTED
FAILURE
SUCCESS
FORGOTTEN
DELAYED
CANCELLED
)
var str = [...]string{
"draft",
"scheduled",
"started",
"failure",
"success",
"forgotten",
"delayed",
"cancelled",
}
func FromInt(i int) string {
return str[i]
}
func (d ScheduledType) String() string {
return str[d]
}
// EnumIndex - Creating common behavior-give the type a EnumIndex functio
func (d ScheduledType) EnumIndex() int {
return int(d)
}

34
models/common/size.go Normal file
View File

@ -0,0 +1,34 @@
package common
type StorageSize int
// StorageType - Enum that defines the type of storage
const (
GB StorageSize = iota
MB
KB
)
var argoType = [...]string{
"Gi",
"Mi",
"Ki",
}
// New creates a new instance of the StorageResource struct
func (dma StorageSize) ToArgo() string {
return argoType[dma]
}
// enum of a data type
type StorageType int
const (
FILE = iota
STREAM
API
DATABASE
S3
MEMORY
HARDWARE
)

View File

@ -2,6 +2,7 @@ package models
import ( import (
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/booking"
@ -27,7 +28,7 @@ var models = map[string]func() utils.DBObject{
tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} }, tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} },
tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} }, tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} },
tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} }, tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} },
tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} }, tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecutions{} },
tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} }, tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} },
tools.RESOURCE_MODEL.String(): func() utils.DBObject { return &resource_model.ResourceModel{} }, tools.RESOURCE_MODEL.String(): func() utils.DBObject { return &resource_model.ResourceModel{} },
tools.PEER.String(): func() utils.DBObject { return &peer.Peer{} }, tools.PEER.String(): func() utils.DBObject { return &peer.Peer{} },
@ -36,6 +37,7 @@ var models = map[string]func() utils.DBObject{
tools.BOOKING.String(): func() utils.DBObject { return &booking.Booking{} }, tools.BOOKING.String(): func() utils.DBObject { return &booking.Booking{} },
tools.WORKFLOW_HISTORY.String(): func() utils.DBObject { return &w2.WorkflowHistory{} }, tools.WORKFLOW_HISTORY.String(): func() utils.DBObject { return &w2.WorkflowHistory{} },
tools.WORKSPACE_HISTORY.String(): func() utils.DBObject { return &w3.WorkspaceHistory{} }, tools.WORKSPACE_HISTORY.String(): func() utils.DBObject { return &w3.WorkspaceHistory{} },
tools.ORDER.String(): func() utils.DBObject { return &order.Order{} },
} }
// Model returns the model object based on the model type // Model returns the model object based on the model type

332
models/order/order.go Normal file
View File

@ -0,0 +1,332 @@
package order
import (
"errors"
"fmt"
"sync"
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/booking"
"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/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools"
)
/*
* Booking is a struct that represents a booking
*/
type OrderStatus = int
const (
DRAFT OrderStatus = iota
PENDING
CANCELLED
PARTIAL
PAID
DISPUTED
OVERDUE
REFUND
)
type Order struct {
utils.AbstractObject
OrderBy string `json:"order_by" bson:"order_by" validate:"required"`
WorkflowExecutionIDs []string `json:"workflow_execution_ids" bson:"workflow_execution_ids" validate:"required"`
Status OrderStatus `json:"status" bson:"status" default:"0"`
SubOrders map[string]*PeerOrder `json:"sub_orders" bson:"sub_orders"`
Total float64 `json:"total" bson:"total" validate:"required"`
}
func (r *Order) StoreDraftDefault() {
r.IsDraft = true
}
func (r *Order) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
if !r.IsDraft && r.Status != set.(*Order).Status {
return true, &Order{Status: set.(*Order).Status} // only state can be updated
}
return r.IsDraft, set
}
func (r *Order) CanDelete() bool {
return r.IsDraft // only draft order can be deleted
}
func (o *Order) DraftOrder(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error {
// set the draft order from the model
if err := o.draftStoreFromModel(scheduler, request); err != nil {
return err
}
return nil
}
func (o *Order) Pay(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error {
if _, err := o.draftBookOrder(scheduler, request); err != nil {
return err
}
o.Status = PENDING
_, code, err := o.GetAccessor(request).UpdateOne(o, o.GetID())
if code != 200 || err != nil {
return errors.New("could not update the order" + fmt.Sprintf("%v", err))
}
if err := o.pay(request); err != nil { // pay the order
return err
} else {
o.IsDraft = false
}
for _, exec := range scheduler.WorkflowExecutions {
exec.IsDraft = false
_, code, err := utils.GenericUpdateOne(exec, exec.GetID(),
workflow_execution.NewAccessor(request), &workflow_execution.WorkflowExecutions{})
if code != 200 || err != nil {
return errors.New("could not update the workflow execution" + fmt.Sprintf("%v", err))
}
}
_, code, err = o.GetAccessor(request).UpdateOne(o, o.GetID())
if code != 200 || err != nil {
return errors.New("could not update the order" + fmt.Sprintf("%v", err))
}
/*
TODO : TEMPORARY SET BOOKINGS TO UNDRAFT TO AVOID DELETION
BUT NEXT ONLY WHO IS PAYED WILL BE ALLOWED TO CHANGE IT
*/
return nil
}
func (o *Order) draftStoreFromModel(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error {
if request == nil {
return errors.New("no request found")
}
if scheduler.Workflow.Graph == nil { // if the workflow has no graph, return an error
return errors.New("no graph found")
}
o.SetName()
o.IsDraft = true
o.OrderBy = request.Username
o.WorkflowExecutionIDs = []string{} // create an array of ids
for _, exec := range scheduler.WorkflowExecutions {
o.WorkflowExecutionIDs = append(o.WorkflowExecutionIDs, exec.GetID())
}
// set the name of the order
resourcesByPeer := map[string][]pricing.PricedItemITF{} // create a map of resources by peer
processings := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsProcessing) // get the processing items
datas := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsData) // get the data items
storages := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsStorage) // get the storage items
workflows := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsWorkflow) // get the workflow items
for _, items := range []map[string]pricing.PricedItemITF{processings, datas, storages, workflows} {
for _, item := range items {
if _, ok := resourcesByPeer[item.GetCreatorID()]; !ok {
resourcesByPeer[item.GetCreatorID()] = []pricing.PricedItemITF{}
}
resourcesByPeer[item.GetCreatorID()] = append(resourcesByPeer[item.GetCreatorID()], item)
}
}
for peerID, resources := range resourcesByPeer {
peerOrder := &PeerOrder{
Status: DRAFT,
PeerID: peerID,
}
peerOrder.GenerateID()
for _, resource := range resources {
peerOrder.AddItem(resource, len(scheduler.WorkflowExecutions)) // TODO SPECIALS REF ADDITIONALS NOTES
}
o.SubOrders[peerOrder.GetID()] = peerOrder
}
// search an order with same user name and same session id
err := o.sumUpBill(request)
if err != nil {
return err
}
// should store the order
res, code, err := o.GetAccessor(request).Search(&dbs.Filters{
And: map[string][]dbs.Filter{
"order_by": {{Operator: dbs.EQUAL.String(), Value: request.Username}},
},
}, "", o.IsDraft)
if code != 200 || err != nil {
return errors.New("could not search the order" + fmt.Sprintf("%v", err))
}
if len(res) > 0 {
_, code, err := utils.GenericUpdateOne(o, res[0].GetID(), o.GetAccessor(request), o)
if code != 200 || err != nil {
return errors.New("could not update the order" + fmt.Sprintf("%v", err))
}
} else {
_, code, err := utils.GenericStoreOne(o, o.GetAccessor(request))
if code != 200 || err != nil {
return errors.New("could not store the order" + fmt.Sprintf("%v", err))
}
}
return nil
}
func (o *Order) draftBookOrder(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) ([]*booking.Booking, error) {
draftedBookings := []*booking.Booking{}
if request == nil {
return draftedBookings, errors.New("no request found")
}
for _, exec := range scheduler.WorkflowExecutions {
bookings := exec.Book(scheduler.Workflow)
for _, booking := range bookings {
_, err := (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "",
tools.BOOKING, tools.POST, booking.Serialize(booking), request.Caller)
if err != nil {
return draftedBookings, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
}
draftedBookings = append(draftedBookings, booking)
}
}
return draftedBookings, nil
}
func (o *Order) Quantity() int {
return len(o.WorkflowExecutionIDs)
}
func (d *Order) SetName() {
d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
}
func (d *Order) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}
func (d *Order) sumUpBill(request *tools.APIRequest) error {
for _, b := range d.SubOrders {
err := b.SumUpBill(request)
if err != nil {
return err
}
d.Total += b.Total
}
return nil
}
// TO FINISH
func (d *Order) pay(request *tools.APIRequest) error {
responses := make(chan *PeerOrder, len(d.SubOrders))
var wg *sync.WaitGroup
wg.Add(len(d.SubOrders))
for _, b := range d.SubOrders {
go b.Pay(request, responses, wg)
}
wg.Wait()
errs := ""
gotAnUnpaid := false
count := 0
for range responses {
res := <-responses
count++
if res != nil {
if res.Error != "" {
errs += res.Error
}
if res.Status != PAID {
gotAnUnpaid = true
}
d.Status = PARTIAL
d.SubOrders[res.GetID()] = res
if count == len(d.SubOrders) && !gotAnUnpaid {
d.Status = PAID
}
}
}
if errs != "" {
return errors.New(errs)
}
return nil
}
type PeerOrder struct {
utils.AbstractObject
Error string `json:"error,omitempty" bson:"error,omitempty"`
PeerID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
Status OrderStatus `json:"status" bson:"status" default:"0"`
BillingAddress string `json:"billing_address,omitempty" bson:"billing_address,omitempty"`
Items []*PeerItemOrder `json:"items,omitempty" bson:"items,omitempty"`
Total float64 `json:"total,omitempty" bson:"total,omitempty"`
}
func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) {
d.Status = PENDING
go func() {
// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
d.Status = PAID // TO REMOVE LATER IT'S A MOCK
if d.Status == PAID {
for _, b := range d.Items {
if !b.Item.IsPurchased(request) {
continue
}
accessor := purchase_resource.NewAccessor(request)
accessor.StoreOne(&purchase_resource.PurchaseResource{
ResourceID: b.Item.GetID(),
ResourceType: b.Item.GetType(),
EndDate: b.Item.GetLocationEnd(),
})
}
}
if d.Status != PENDING {
response <- d
}
wg.Done()
}()
}
func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
for _, b := range d.Items {
tot, err := b.GetPrice(request) // missing something
if err != nil {
return err
}
d.Total += tot
}
return nil
}
func (d *PeerOrder) AddItem(item pricing.PricedItemITF, quantity int) {
d.Items = append(d.Items, &PeerItemOrder{
Quantity: quantity,
Item: item,
})
}
func (d *PeerOrder) SetName() {
d.Name = d.UUID + "_order_" + d.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
}
type PeerItemOrder struct {
Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"`
Purchase purchase_resource.PurchaseResource `json:"purchase,omitempty" bson:"purchase,omitempty"`
Item pricing.PricedItemITF `json:"item,omitempty" bson:"item,omitempty"`
}
func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
accessor := purchase_resource.NewAccessor(request)
search, code, _ := accessor.Search(&dbs.Filters{
And: map[string][]dbs.Filter{
"resource_id": {{Operator: dbs.EQUAL.String(), Value: d.Item.GetID()}},
},
}, "", d.Purchase.IsDraft)
if code == 200 && len(search) > 0 {
for _, s := range search {
if s.(*purchase_resource.PurchaseResource).EndDate == nil || time.Now().UTC().After(*s.(*purchase_resource.PurchaseResource).EndDate) {
return 0, nil
}
}
}
p, err := d.Item.GetPrice(request)
if err != nil {
return 0, err
}
return p * float64(d.Quantity), nil
}
// SHOULD SET A BUYING STATUS WHEN PAYMENT IS VALIDATED

View File

@ -0,0 +1,64 @@
package order
import (
"errors"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type orderMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}
// New creates a new instance of the orderMongoAccessor
func NewAccessor(request *tools.APIRequest) *orderMongoAccessor {
return &orderMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type
Request: request,
Type: tools.ORDER,
},
}
}
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *orderMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *orderMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, a, &Order{})
}
func (a *orderMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("Not implemented")
}
func (a *orderMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("Not implemented")
}
func (a *orderMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Order](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *orderMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Order](a.getExec(), isDraft, a)
}
func (a *orderMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Order](filters, search, (&Order{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *orderMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@ -29,10 +29,11 @@ func (m PeerState) EnumIndex() int {
// Peer is a struct that represents a peer // Peer is a struct that represents a peer
type Peer struct { type Peer struct {
utils.AbstractObject utils.AbstractObject
Url string `json:"url,omitempty" bson:"url,omitempty" validate:"required"` // Url is the URL of the peer (base64url) Url string `json:"url" bson:"url" validate:"required"` // Url is the URL of the peer (base64url)
PublicKey string `json:"public_key,omitempty" bson:"public_key,omitempty"` // PublicKey is the public key of the peer WalletAddress string `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
Services map[string]int `json:"services,omitempty" bson:"services,omitempty"` PublicKey string `json:"public_key" bson:"public_key" validate:"required"` // PublicKey is the public key of the peer
State PeerState `json:"state" bson:"state" default:"0"` State PeerState `json:"state" bson:"state" default:"0"`
ServicesState map[string]int `json:"services_state,omitempty" bson:"services_state,omitempty"`
FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried
} }
@ -62,13 +63,13 @@ func (ao *Peer) RemoveExecution(exec PeerExecution) {
} }
// IsMySelf checks if the peer is the local peer // IsMySelf checks if the peer is the local peer
func (ao *Peer) IsMySelf() (bool, string) { func (p *Peer) IsMySelf() (bool, string) {
d, code, err := New(tools.PEER, "", "", nil, nil).Search(nil, SELF.String()) d, code, err := NewAccessor(nil).Search(nil, SELF.String(), p.IsDraft)
if code != 200 || err != nil || len(d) == 0 { if code != 200 || err != nil || len(d) == 0 {
return false, "" return false, ""
} }
id := d[0].GetID() id := d[0].GetID()
return ao.UUID == id, id return p.UUID == id, id
} }
// LaunchPeerExecution launches an execution on a peer // LaunchPeerExecution launches an execution on a peer
@ -76,11 +77,11 @@ func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataTy
p.UUID = peerID p.UUID = peerID
return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache
} }
func (d *Peer) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *Peer) GetAccessor(request *tools.APIRequest) utils.Accessor {
data := New(tools.PEER, username, peerID, groups, caller) // Create a new instance of the accessor data := NewAccessor(request) // Create a new instance of the accessor
return data return data
} }
func (d *Peer) VerifyAuth(username string, peerID string, groups []string) bool { func (r *Peer) CanDelete() bool {
return true return false // only draft order can be deleted
} }

View File

@ -56,7 +56,7 @@ func (p *PeerCache) urlFormat(url string, dt tools.DataType) string {
// checkPeerStatus checks the status of a peer // checkPeerStatus checks the status of a peer
func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools.HTTPCaller) (*Peer, bool) { func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools.HTTPCaller) (*Peer, bool) {
api := tools.API{} api := tools.API{}
access := NewShallow() access := NewShallowAccessor()
res, code, _ := access.LoadOne(peerID) // Load the peer from db res, code, _ := access.LoadOne(peerID) // Load the peer from db
if code != 200 { // no peer no party if code != 200 { // no peer no party
return nil, false return nil, false
@ -73,7 +73,7 @@ func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools
fmt.Println("Checking peer status on", url, "...") fmt.Println("Checking peer status on", url, "...")
state, services := api.CheckRemotePeer(url) state, services := api.CheckRemotePeer(url)
fmt.Println("Checking peer status on", url, state, services) // Check the status of the peer fmt.Println("Checking peer status on", url, state, services) // Check the status of the peer
res.(*Peer).Services = services // Update the services states of the peer res.(*Peer).ServicesState = services // Update the services states of the peer
access.UpdateOne(res, peerID) // Update the peer in the db access.UpdateOne(res, peerID) // Update the peer in the db
return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status
} }
@ -101,18 +101,18 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
DataID: dataID, DataID: dataID,
} }
mypeer.AddExecution(*pexec) mypeer.AddExecution(*pexec)
NewShallow().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
return nil, errors.New("peer is not reachable") return nil, errors.New("peer is not reachable")
} else { } else {
if mypeer == nil { if mypeer == nil {
return nil, errors.New("peer not found") return nil, errors.New("peer not found")
} }
// If the peer is reachable, launch the execution // If the peer is reachable, launch the execution
url = p.urlFormat((mypeer.Url)+meth, dt) // Format the URL url = p.urlFormat((mypeer.Url)+meth, dt) // Format the URL
tmp := mypeer.FailedExecution // Get the failed executions list tmp := mypeer.FailedExecution // Get the failed executions list
mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list
NewShallow().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
for _, v := range tmp { // Retry the failed executions for _, v := range tmp { // Retry the failed executions
go p.exec(v.Url, tools.ToMethod(v.Method), v.Body, caller) go p.exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
} }
} }

View File

@ -14,7 +14,7 @@ type peerMongoAccessor struct {
} }
// New creates a new instance of the peerMongoAccessor // New creates a new instance of the peerMongoAccessor
func NewShallow() *peerMongoAccessor { func NewShallowAccessor() *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
@ -23,15 +23,12 @@ func NewShallow() *peerMongoAccessor {
} }
} }
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *peerMongoAccessor { func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: tools.PEER,
User: username,
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
@ -62,17 +59,17 @@ func (dca *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
}, dca) }, dca)
} }
func (wfa *peerMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, wfa) }, isDraft, wfa)
} }
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Peer](filters, search, wfa.getDefaultFilter(search), return utils.GenericSearch[*Peer](filters, search, wfa.getDefaultFilter(search),
func(d utils.DBObject) utils.ShallowDBObject { func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, wfa) }, isDraft, wfa)
} }
func (a *peerMongoAccessor) getDefaultFilter(search string) *dbs.Filters { func (a *peerMongoAccessor) getDefaultFilter(search string) *dbs.Filters {
s, err := strconv.Atoi(search) s, err := strconv.Atoi(search)

View File

@ -1,7 +1,12 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "errors"
"strings"
"time"
"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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -11,68 +16,152 @@ import (
* it defines the resource compute * it defines the resource compute
*/ */
type ComputeResource struct { type ComputeResource struct {
resource_model.AbstractResource AbstractResource[*ComputeResourceInstance]
Technology TechnologyEnum `json:"technology" bson:"technology" default:"0"` // Technology is the technology Architecture string `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
Architecture string `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture Infrastructure common.InfrastructureType `json:"infrastructure,omitempty" bson:"infrastructure,omitempty"`
Access AccessEnum `json:"access" bson:"access" default:"0"` // Access is the access
Localisation string `json:"localisation,omitempty" bson:"localisation,omitempty"` // Localisation is the localisation
CPUs []*CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs
RAM *RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM
GPUs []*GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs
} }
func (d *ComputeResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *ComputeResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New[*ComputeResource](tools.COMPUTE_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &ComputeResource{} }) return NewAccessor[*ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &ComputeResource{} })
} }
// CPU is a struct that represents a CPU type ComputeResourceInstance struct {
type CPU struct { ResourceInstance[*ComputeResourcePartnership]
Cores uint `bson:"cores,omitempty" json:"cores,omitempty"` //TODO: validate SecurityLevel string `json:"security_level,omitempty" bson:"security_level,omitempty"`
Architecture string `bson:"architecture,omitempty" json:"architecture,omitempty"` //TOOD: enum PowerSource string `json:"power_source,omitempty" bson:"power_source,omitempty"`
Shared bool `bson:"shared,omitempty" json:"shared,omitempty"` CPUs map[string]*common.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
MinimumMemory uint `bson:"minimum_memory,omitempty" json:"minimum_memory,omitempty"` GPUs map[string]*common.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
Platform string `bson:"platform,omitempty" json:"platform,omitempty"` RAM *common.RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM
} }
type RAM struct { type ComputeResourcePartnership struct {
Size uint `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"` ResourcePartnerShip[*ComputeResourcePricingProfile]
Ecc bool `bson:"ecc,omitempty" json:"ecc,omitempty"` MaxAllowedCPUsCores map[string]int `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"`
MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"`
MaxAllowedRAMSize float64 `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"`
} }
type GPU struct { type ComputeResourcePricingProfileOptions struct {
CudaCores uint `bson:"cuda_cores,omitempty" json:"cuda_cores,omitempty"` CPUCore int `json:"cpu_core" bson:"cpu_core" default:"1"`
Model string `bson:"model,omitempty" json:"model,omitempty"` GPUMemoryGB float64 `json:"gpu_memory_gb" bson:"gpu_memory_gb" default:"1"`
Memory uint `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"` RAMSizeGB float64 `json:"ram_size_gb" bson:"ram_size_gb" default:"1"`
TensorCores uint `bson:"tensor_cores,omitempty" json:"tensor_cores,omitempty"`
} }
type TechnologyEnum int type ComputeResourcePricingProfile struct {
pricing.ExploitPricingProfile[pricing.TimePricingStrategy]
const ( Options ComputeResourcePricingProfileOptions `json:"options,omitempty" bson:"options,omitempty"` // Options is the options of the pricing profile
DOCKER TechnologyEnum = iota // ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time
KUBERNETES OverrideCPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs
SLURM OverrideGPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs
HW OverrideRAMPrice float64 `json:"ram_price" bson:"ram_price" default:"-1"` // RAMPrice is the price of the RAM
CONDOR
)
func (t TechnologyEnum) String() string {
return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
} }
type AccessEnum int // PROBLEM
const ( func (p *ComputeResourcePricingProfile) IsPurchased() bool {
SSH AccessEnum = iota return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
SSH_KUBE_API }
SSH_SLURM
SSH_DOCKER func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
OPENCLOUD return -1
VPN }
)
// NOT A PROPER QUANTITY
func (a AccessEnum) String() string { // amountOfData is the number of CPUs, GPUs or RAM dependings on the params
return [...]string{"SSH", "SSH_KUBE_API", "SSH_SLURM", "SSH_DOCKER", "OPENCLOUD", "VPN"}[a] func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
if len(params) < 1 {
return 0, errors.New("params must be set")
}
pp := float64(0)
model := params[1]
if strings.Contains(params[0], "cpus") && len(params) > 1 {
if _, ok := p.OverrideCPUsPrices[model]; ok {
p.Pricing.Price = p.OverrideCPUsPrices[model]
}
r, err := p.Pricing.GetPrice(amountOfData/float64(p.Options.CPUCore), explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
if strings.Contains(params[0], "gpus") && len(params) > 1 {
if _, ok := p.OverrideGPUsPrices[model]; ok {
p.Pricing.Price = p.OverrideGPUsPrices[model]
}
r, err := p.Pricing.GetPrice(amountOfData/float64(p.Options.GPUMemoryGB), explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
if strings.Contains(params[0], "ram") {
if p.OverrideRAMPrice >= 0 {
p.Pricing.Price = p.OverrideRAMPrice
}
r, err := p.Pricing.GetPrice(float64(amountOfData)/p.Options.RAMSizeGB, explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
return pp, nil
}
type CustomizedComputeResource struct {
AbstractCustomizedResource[*ComputeResourceInstance]
CPUsLocated map[string]float64 `json:"cpus_in_use" bson:"cpus_in_use"` // CPUsInUse is the list of CPUs in use
GPUsLocated map[string]float64 `json:"gpus_in_use" bson:"gpus_in_use"` // GPUsInUse is the list of GPUs in use
RAMLocated float64 `json:"ram_in_use" bson:"ram_in_use"` // RAMInUse is the RAM in use
}
func (r *CustomizedComputeResource) GetType() tools.DataType {
return tools.COMPUTE_RESOURCE
}
func (r *CustomizedComputeResource) GetPrice(request *tools.APIRequest) (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("Usage start and end must be set")
}
partner := r.GetPartnership(request)
if partner != nil && partner.GetPricing(r.SelectedPricing) != nil {
return 0, errors.New("Pricing strategy not found")
}
pricing := partner.GetPricing(r.SelectedPricing)
price := float64(0)
for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
for model, amountOfData := range l {
cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request, "cpus", model)
if err != nil {
return 0, err
}
price += cpus
}
}
ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request, "ram")
if err != nil {
return 0, err
}
price += ram
return price, nil
}
/*
* FillWithDefaultProcessingUsage fills the order item with the default processing usage
* it depends on the processing usage only if nothing is set, during order
*/
func (i *CustomizedComputeResource) FillWithDefaultProcessingUsage(usage *ProcessingUsage) {
for _, cpu := range usage.CPUs {
if _, ok := i.CPUsLocated[cpu.Model]; !ok {
i.CPUsLocated[cpu.Model] = 0
}
if i.CPUsLocated[cpu.Model] < float64(cpu.Cores) {
i.CPUsLocated[cpu.Model] = float64(cpu.Cores)
}
}
for _, cpu := range usage.GPUs {
i.GPUsLocated[cpu.Model] = 1
}
i.RAMLocated = usage.RAM.SizeGb
} }

View File

@ -1,7 +1,10 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "errors"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -15,28 +18,114 @@ const (
LICENCED LICENCED
) )
/*
* Struct of Usage Conditions
*/
type UsageConditions struct {
Usage string `json:"usage,omitempty" bson:"usage,omitempty" description:"usage of the data"` // Usage is the usage of the data
Actors []string `json:"actors,omitempty" bson:"actors,omitempty" description:"actors of the data"` // Actors is the actors of the data
}
/* /*
* DataResource is a struct that represents a data resource * DataResource is a struct that represents a data resource
* it defines the resource data * it defines the resource data
*/ */
type DataResource struct { type DataResource struct {
resource_model.AbstractResource // AbstractResource contains the basic fields of an object (id, name) AbstractResource[*ResourceInstance[*DataResourcePartnership]]
resource_model.WebResource Type string `bson:"type,omitempty" json:"type,omitempty"`
Type string `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage Quality string `bson:"quality,omitempty" json:"quality,omitempty"`
UsageConditions UsageConditions `json:"usage_conditions,omitempty" bson:"usage_conditions,omitempty" description:"usage conditions of the data"` // UsageConditions is the usage conditions of the data OpenData bool `bson:"open_data" json:"open_data" default:"false"` // Type is the type of the storage
License DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data Static bool `bson:"static" json:"static" default:"false"`
Interest DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"` // Interest is the interest of the data UpdatePeriod time.Time `bson:"update_period,omitempty" json:"update_period,omitempty"`
Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data PersonalData bool `bson:"personal_data,omitempty" json:"personal_data,omitempty"`
AnonymizedPersonalData bool `bson:"anonymized_personal_data,omitempty" json:"anonymized_personal_data,omitempty"`
SizeGB float64 `json:"size_gb,omitempty" bson:"size_gb,omitempty"` // SizeGB is the size of the data License DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data
// ? Interest DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"` // Interest is the interest of the data
Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data
} }
func (d *DataResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *DataResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New[*DataResource](tools.DATA_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor return NewAccessor[*DataResource](tools.DATA_RESOURCE, request, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor
}
type DataResourcePartnership struct {
ResourcePartnerShip[*DataResourcePricingProfile]
MaxDownloadableGbAllowed float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
PersonalDataAllowed bool `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
AnonymizedPersonalDataAllowed bool `json:"anonymized_personal_data_allowed,omitempty" bson:"anonymized_personal_data_allowed,omitempty"`
}
type DataResourcePricingStrategy int
const (
PER_DOWNLOAD DataResourcePricingStrategy = iota
PER_TB_DOWNLOADED
PER_GB_DOWNLOADED
PER_MB_DOWNLOADED
PER_KB_DOWNLOADED
)
func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
return DataResourcePricingStrategy(i)
}
func (t DataResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DOWNLOAD", "PER_GB", "PER_MB", "PER_KB"}[t]
}
func (t DataResourcePricingStrategy) GetStrategyValue() int {
return int(t)
}
func (t DataResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
switch t {
case PER_DOWNLOAD:
return 1, nil
case PER_TB_DOWNLOADED:
return amountOfDataGB * 1000, nil
case PER_GB_DOWNLOADED:
return amountOfDataGB, nil
case PER_MB_DOWNLOADED:
return amountOfDataGB / 1000, nil
case PER_KB_DOWNLOADED:
return amountOfDataGB / 1000000, nil
}
return 0, errors.New("Pricing strategy not found")
}
type DataResourcePricingProfile struct {
pricing.AccessPricingProfile[DataResourcePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
}
func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
return p.Pricing.OverrideStrategy.GetStrategyValue()
}
func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
}
func (p *DataResourcePricingProfile) IsPurchased() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}
type CustomizedDataResource struct {
AbstractCustomizedResource[*ResourceInstance[*DataResourcePartnership]]
StorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
}
func (r *CustomizedDataResource) GetType() tools.DataType {
return tools.DATA_RESOURCE
}
func (r *CustomizedDataResource) GetPrice(request *tools.APIRequest) (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("Usage start and end must be set")
}
partner := r.GetPartnership(request)
if partner != nil && partner.GetPricing(r.SelectedPricing) != nil {
return 0, errors.New("Pricing strategy not found")
}
pricing := partner.GetPricing(r.SelectedPricing)
var err error
amountOfData := float64(1)
if pricing.GetOverrideStrategyValue() >= 0 {
amountOfData, err = ToDataResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.StorageGB)
if err != nil {
return 0, err
}
}
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request)
} }

View File

@ -0,0 +1,47 @@
package resources
import (
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type ShallowResourceInterface interface {
utils.DBObject
GetType() tools.DataType
GetCreatorID() string
GetPricingID() string
GetLocationStart() *time.Time
GetLocationEnd() *time.Time
GetExplicitDurationInS() float64
SetStartUsage(start time.Time)
SetEndUsage(end time.Time)
GetPartnership(request *tools.APIRequest) ResourcePartnerITF
SetResourceModel(model *resource_model.ResourceModel)
}
type ResourceInterface interface {
utils.DBObject
Trim()
GetCreatorID() string
VerifyPartnerships() bool
GetPartnership(request *tools.APIRequest) ResourcePartnerITF
SetAllowedInstances(request *tools.APIRequest)
SetResourceModel(model *resource_model.ResourceModel)
}
type InstanceITF interface {
GetID() string
VerifyPartnerships() bool
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups()
}
type ResourcePartnerITF interface {
GetPricing(id string) pricing.PricingProfileITF
GetPeerGroups() map[string][]string
ClearPeerGroups()
}

View File

@ -0,0 +1,92 @@
package resources
import (
"time"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type ExploitedResourceSet struct {
DataResources []*CustomizedDataResource `bson:"-" json:"data_resources,omitempty"`
StorageResources []*CustomizedStorageResource `bson:"-" json:"storage_resources,omitempty"`
ProcessingResources []*CustomizedProcessingResource `bson:"-" json:"processing_resources,omitempty"`
ComputeResources []*CustomizedComputeResource `bson:"-" json:"compute_resources,omitempty"`
WorkflowResources []*CustomizedWorkflowResource `bson:"-" json:"workflow_resources,omitempty"`
}
type ResourceSet struct {
Datas []string `bson:"datas,omitempty" json:"datas,omitempty"`
Storages []string `bson:"storages,omitempty" json:"storages,omitempty"`
Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
Computes []string `bson:"computes,omitempty" json:"computes,omitempty"`
Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"`
StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"`
ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"`
WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"`
}
func (r *ResourceSet) Clear() {
r.DataResources = nil
r.StorageResources = nil
r.ProcessingResources = nil
r.ComputeResources = nil
r.WorkflowResources = nil
}
func (r *ResourceSet) Fill(request *tools.APIRequest) {
for k, v := range map[utils.DBObject][]string{
(&DataResource{}): r.Datas,
(&ComputeResource{}): r.Computes,
(&StorageResource{}): r.Storages,
(&ProcessingResource{}): r.Processings,
(&WorkflowResource{}): r.Workflows,
} {
for _, id := range v {
d, _, e := k.GetAccessor(request).LoadOne(id)
if e == nil {
switch k.(type) {
case *DataResource:
r.DataResources = append(r.DataResources, d.(*DataResource))
case *ComputeResource:
r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource))
case *StorageResource:
r.StorageResources = append(r.StorageResources, d.(*StorageResource))
case *ProcessingResource:
r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource))
case *WorkflowResource:
r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource))
}
}
}
}
}
type ItemExploitedResource struct {
Data *CustomizedDataResource `bson:"data,omitempty" json:"data,omitempty"`
Processing *CustomizedProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"`
Storage *CustomizedStorageResource `bson:"storage,omitempty" json:"storage,omitempty"`
Compute *CustomizedComputeResource `bson:"compute,omitempty" json:"compute,omitempty"`
Workflow *CustomizedWorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"`
}
func (w *ItemExploitedResource) SetItemEndUsage(end time.Time) {
for _, item := range []ShallowResourceInterface{w.Data, w.Processing, w.Storage, w.Compute, w.Workflow} {
if item != nil {
item.SetItemEndUsage(end)
}
}
}
func (w *ItemExploitedResource) SetItemStartUsage(start time.Time) {
for _, item := range []ShallowResourceInterface{w.Data, w.Processing, w.Storage, w.Compute, w.Workflow} {
if item != nil {
item.SetItemStartUsage(start)
}
}
}

View File

@ -0,0 +1,6 @@
package priced_resource
type PricedResource struct {
}
/*TODO*/

View File

@ -1,23 +1,22 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "time"
"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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type Container struct { type ProcessingUsage struct {
Image string `json:"image,omitempty" bson:"image,omitempty"` // Image is the container image CPUs map[string]*common.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
Command string `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command GPUs map[string]*common.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
Args string `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments RAM *common.RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM
Env map[string]string `json:"env,omitempty" bson:"env,omitempty"` // Env is the container environment variables
Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes
}
type Expose struct { StorageGb float64 `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage
Port int `json:"port,omitempty" bson:"port,omitempty"` // Port is the port Hypothesis string `bson:"hypothesis,omitempty" json:"hypothesis,omitempty"`
Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse ScalingModel string `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model
PAT int `json:"pat,omitempty" bson:"pat,omitempty"` // PAT is the PAT
} }
/* /*
@ -25,19 +24,50 @@ type Expose struct {
* it defines the resource processing * it defines the resource processing
*/ */
type ProcessingResource struct { type ProcessingResource struct {
resource_model.AbstractResource AbstractResource[*ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]]
IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service Infrastructure common.InfrastructureType `json:"infrastructure,omitempty" bson:"infrastructure,omitempty"`
CPUs []*CPU `bson:"cpus,omitempty" json:"cp_us,omitempty"` // CPUs is the list of CPUs IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service
GPUs []*GPU `bson:"gpus,omitempty" json:"gp_us,omitempty"` // GPUs is the list of GPUs Usage *ProcessingUsage `bson:"usage,omitempty" json:"usage,omitempty"` // Usage is the usage of the processing
RAM *RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM OpenSource bool `json:"open_source" bson:"open_source" default:"false"`
Storage uint `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage License string `json:"license,omitempty" bson:"license,omitempty"`
Parallel bool `bson:"parallel,omitempty" json:"parallel,omitempty"` // Parallel is a flag that indicates if the processing is parallel Maturity string `json:"maturity,omitempty" bson:"maturity,omitempty"`
ScalingModel uint `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model Container common.Container `json:"container,omitempty" bson:"container,omitempty"` // Container is the container
DiskIO string `bson:"disk_io,omitempty" json:"disk_io,omitempty"` // DiskIO is the disk IO
Container *Container `bson:"container,omitempty" json:"container,omitempty"` // Container is the container
Expose []Expose `bson:"expose,omitempty" json:"expose,omitempty"` // Expose is the execution
} }
func (d *ProcessingResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { type CustomizedProcessingResource struct {
return New[*ProcessingResource](tools.PROCESSING_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor AbstractCustomizedResource[*ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]]
IsService bool
}
func (r *CustomizedProcessingResource) GetType() tools.DataType {
return tools.PROCESSING_RESOURCE
}
func (a *CustomizedProcessingResource) GetExplicitDurationInS() float64 {
if a.ExplicitBookingDurationS == 0 {
if a.IsService || a.UsageStart == nil {
if a.IsService {
return -1
}
return time.Duration(1 * time.Hour).Seconds()
}
return a.UsageEnd.Sub(*a.UsageStart).Seconds()
}
return a.ExplicitBookingDurationS
}
func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor[*ProcessingResource](tools.PROCESSING_RESOURCE, request, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor
}
type ProcessingResourcePricingProfile struct {
pricing.AccessPricingProfile[pricing.TimePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
}
func (p *ProcessingResourcePricingProfile) IsPurchased() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end)
} }

View File

@ -0,0 +1,30 @@
package purchase_resource
import (
"time"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type PurchaseResource struct {
utils.AbstractObject
EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"`
ResourceType tools.DataType `json:"resource_type" bson:"resource_type" validate:"required"`
}
func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}
func (r *PurchaseResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
return r.IsDraft, set // only draft buying can be updated
}
func (r *PurchaseResource) CanDelete() bool { // ENDBuyingDate is passed
if r.EndDate != nil {
return time.Now().UTC().After(*r.EndDate)
}
return false // only draft bookings can be deleted
}

View File

@ -0,0 +1,72 @@
package purchase_resource
import (
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type purchaseResourceMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}
// New creates a new instance of the bookingMongoAccessor
func NewAccessor(request *tools.APIRequest) *purchaseResourceMongoAccessor {
return &purchaseResourceMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.BUYING_STATUS.String()), // Create a logger with the data type
Request: request,
Type: tools.BUYING_STATUS,
},
}
}
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *purchaseResourceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *purchaseResourceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, a, &PurchaseResource{})
}
func (a *purchaseResourceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *purchaseResourceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *purchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*PurchaseResource](id, func(d utils.DBObject) (utils.DBObject, int, error) {
if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
utils.GenericDeleteOne(id, a)
return nil, 404, nil
}
return d, 200, nil
}, a)
}
func (a *purchaseResourceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*PurchaseResource](a.getExec(), isDraft, a)
}
func (a *purchaseResourceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*PurchaseResource](filters, search, (&PurchaseResource{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *purchaseResourceMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
utils.GenericDeleteOne(d.GetID(), a)
return nil
}
return d
}
}

View File

@ -1,8 +1,18 @@
package resources package resources
import ( import (
"errors"
"slices"
"time"
"cloud.o-forge.io/core/oc-lib/config"
"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/resource_model" "cloud.o-forge.io/core/oc-lib/models/resources/resource_model"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/biter777/countries"
"github.com/marcinwyszynski/geopoint"
) )
// AbstractResource is the struct containing all of the attributes commons to all ressources // AbstractResource is the struct containing all of the attributes commons to all ressources
@ -10,86 +20,260 @@ import (
// Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior // Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior
// http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang // http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang
type ResourceInterface interface { /*
utils.DBObject * AbstractResource is a struct that represents a resource
Trim() *resource_model.AbstractResource * it defines the resource data
SetResourceModel(model *resource_model.ResourceModel) */
type abstractResource struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource
ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource
ResourceModel *resource_model.ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"` // ResourceModel is the model of the resource
UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
} }
type ResourceSet struct { func (r *abstractResource) StoreDraftDefault() {
Datas []string `bson:"datas,omitempty" json:"datas,omitempty"` r.IsDraft = true
Storages []string `bson:"storages,omitempty" json:"storages,omitempty"`
Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
Computes []string `bson:"computes,omitempty" json:"computes,omitempty"`
Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"`
StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"`
ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"`
WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"`
} }
func (r *ResourceSet) Clear() { func (r *AbstractCustomizedResource[T]) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
r.DataResources = nil if r.IsDraft != set.IsDrafted() && set.IsDrafted() {
r.StorageResources = nil return true, set // only state can be updated
r.ProcessingResources = nil }
r.ComputeResources = nil return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
r.WorkflowResources = nil
} }
func (r *ResourceSet) Fill(username string, peerID string, groups []string) { func (r *abstractResource) CanDelete() bool {
for k, v := range map[utils.DBObject][]string{ return r.IsDraft // only draft bookings can be deleted
(&DataResource{}): r.Datas, }
(&ComputeResource{}): r.Computes,
(&StorageResource{}): r.Storages, func (ao *abstractResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
(&ProcessingResource{}): r.Processings, return nil
(&WorkflowResource{}): r.Workflows, }
} {
for _, id := range v { func (ao *abstractResource) GetCreatorID() string {
d, _, e := k.GetAccessor(username, peerID, groups, nil).LoadOne(id) return ao.CreatorID
if e == nil { }
switch k.(type) {
case *DataResource: func (abs *abstractResource) SetResourceModel(model *resource_model.ResourceModel) {
r.DataResources = append(r.DataResources, d.(*DataResource)) abs.ResourceModel = model
case *ComputeResource: }
r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource))
case *StorageResource: type AbstractResource[T InstanceITF] struct {
r.StorageResources = append(r.StorageResources, d.(*StorageResource)) abstractResource
case *ProcessingResource: Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource)) }
case *WorkflowResource:
r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource)) func (abs *AbstractResource[T]) SetAllowedInstances(request *tools.APIRequest) {
} abs.Instances = verifyAuthAction[T](abs.Instances, request)
}
func (abs *AbstractResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF {
for _, instance := range abs.Instances {
partners, grps := instance.GetPeerGroups()
for i, p := range grps {
if request == nil {
continue
} }
if _, ok := p[request.PeerID]; ok {
return partners[i]
}
}
}
return nil
}
func (abs *AbstractResource[T]) VerifyPartnerships() bool {
// a peer can be part of only one partnership by instance
// may we need to define partnership in a different DB
for _, instance := range abs.Instances {
if !instance.VerifyPartnerships() {
return false
}
}
return true
}
func (d *AbstractResource[T]) Trim() {
if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok {
// TODO clean up the peer groups that are not in the allowed peers group
for _, instance := range d.Instances {
instance.ClearPeerGroups()
} }
} }
} }
type ItemResource struct { func (abs *AbstractResource[T]) VerifyAuth(request *tools.APIRequest) bool {
Data *DataResource `bson:"data,omitempty" json:"data,omitempty"` return len(verifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request)
Processing *ProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"`
Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"`
Compute *ComputeResource `bson:"compute,omitempty" json:"compute,omitempty"`
Workflow *WorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"`
} }
func (i *ItemResource) GetAbstractRessource() *resource_model.AbstractResource { type AbstractCustomizedResource[T InstanceITF] struct {
abstractResource
ExplicitBookingDurationS float64 `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"`
UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"`
SelectedInstance T `json:"selected_instance,omitempty" bson:"selected_instance,omitempty"`
SelectedPricing string `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
}
if i.Data != nil { func (abs *AbstractCustomizedResource[T]) SetStartUsage(start time.Time) {
return &i.Data.AbstractResource if abs.UsageStart == nil {
abs.UsageStart = &start
} }
if i.Processing != nil { }
return &i.Processing.AbstractResource
func (abs *AbstractCustomizedResource[T]) SetEndUsage(end time.Time) {
if abs.UsageEnd == nil {
abs.UsageEnd = &end
} }
if i.Storage != nil { }
return &i.Storage.AbstractResource
func (abs *AbstractCustomizedResource[T]) IsPurchased(request *tools.APIRequest) bool {
return abs.GetPartnership(request).GetPricing(abs.SelectedPricing).IsPurchased()
}
func (abs *AbstractCustomizedResource[T]) GetLocationEnd() *time.Time {
return abs.UsageEnd
}
func (abs *AbstractCustomizedResource[T]) GetLocationStart() *time.Time {
return abs.UsageStart
}
func (abs *AbstractCustomizedResource[T]) GetExplicitDurationInS() float64 {
if abs.ExplicitBookingDurationS == 0 {
if abs.UsageEnd == nil || abs.UsageStart == nil {
return time.Duration(1 * time.Hour).Seconds()
}
return abs.UsageEnd.Sub(*abs.UsageStart).Seconds()
} }
if i.Compute != nil { return abs.ExplicitBookingDurationS
return &i.Compute.AbstractResource }
func (abs *AbstractCustomizedResource[T]) GetPricingID() string {
return abs.SelectedPricing
}
func (r *AbstractCustomizedResource[T]) GetPrice(request *tools.APIRequest) (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("Usage start and end must be set")
} }
if i.Workflow != nil { partner := r.GetPartnership(request)
return &i.Workflow.AbstractResource if partner != nil && partner.GetPricing(r.SelectedPricing) != nil {
return 0, errors.New("Pricing strategy not found")
}
return partner.GetPricing(r.SelectedPricing).GetPrice(1, 0, *r.UsageStart, *r.UsageEnd, request)
}
func (abs *AbstractCustomizedResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF {
partners, grps := abs.SelectedInstance.GetPeerGroups()
for i, p := range grps {
if request == nil {
continue
}
if _, ok := p[request.PeerID]; ok {
return partners[i]
}
} }
return nil return nil
} }
func verifyAuthAction[T InstanceITF](baseInstance []T, request *tools.APIRequest) []T {
instances := []T{}
for _, instance := range baseInstance {
_, peerGroups := instance.GetPeerGroups()
for _, peers := range peerGroups {
if request == nil {
continue
}
if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist {
if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
instances = append(instances, instance)
}
for _, grp := range grps {
if slices.Contains(request.Groups, grp) {
instances = append(instances, instance)
}
}
}
}
}
return instances
}
type ResourceInstance[T ResourcePartnerITF] struct {
UUID string `json:"id,omitempty" bson:"id,omitempty"`
Location geopoint.GeoPoint `json:"location,omitempty" bson:"location,omitempty"`
Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
// Url string `json:"url,omitempty" bson:"url,omitempty"`
AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
Partnerships []T `json:"partner_resource,omitempty" bson:"partner_resource,omitempty"`
}
func (ri *ResourceInstance[T]) GetID() string {
return ri.UUID
}
func (r *ResourceInstance[T]) VerifyPartnerships() bool {
peersMultiple := map[string]int{}
for _, p := range r.Partnerships {
for k, g := range p.GetPeerGroups() {
for _, v := range g {
if _, ok := peersMultiple[k+"_"+v]; !ok {
peersMultiple[k+"_"+v] = 0
}
peersMultiple[k+"_"+v]++
}
}
}
for _, p := range peersMultiple {
if p > 1 {
return false
}
}
return true
}
func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) {
groups := []map[string][]string{}
partners := []ResourcePartnerITF{}
for _, p := range ri.Partnerships {
partners = append(partners, p)
groups = append(groups, p.GetPeerGroups())
}
return partners, groups
}
func (ri *ResourceInstance[T]) ClearPeerGroups() {
for _, p := range ri.Partnerships {
p.ClearPeerGroups()
}
}
type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
Namespace string `json:"namespace" bson:"namespace" default:"default-namespace"`
PeerGroups map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"`
PricingProfiles map[string]T `json:"pricing,omitempty" bson:"pricing,omitempty"`
}
func (rp *ResourcePartnerShip[T]) GetPricing(id string) pricing.PricingProfileITF {
return rp.PricingProfiles[id]
}
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
return rp.PeerGroups
}
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
rp.PeerGroups = map[string][]string{}
}
/*
-> when a workflow should book a resource
-> it must be able to book a resource for a particular time
-> the resource should be available for the time
-> we must be able to parameter the resource for the time
-> before bookin'
*/

View File

@ -1,6 +1,8 @@
package resources package resources
import ( import (
"slices"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "cloud.o-forge.io/core/oc-lib/models/resources/resource_model"
@ -14,15 +16,15 @@ type resourceMongoAccessor[T ResourceInterface] struct {
} }
// New creates a new instance of the computeMongoAccessor // New creates a new instance of the computeMongoAccessor
func New[T ResourceInterface](t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller, g func() utils.DBObject) *resourceMongoAccessor[T] { func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIRequest, g func() utils.DBObject) *resourceMongoAccessor[T] {
if !slices.Contains([]tools.DataType{tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE, tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE, tools.DATA_RESOURCE}, t) {
return nil
}
return &resourceMongoAccessor[T]{ return &resourceMongoAccessor[T]{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
ResourceModelAccessor: resource_model.New(), ResourceModelAccessor: resource_model.NewAccessor(),
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID,
User: username, // Set the caller
Groups: groups, // Set the caller
Type: t, Type: t,
}, },
generateData: g, generateData: g,
@ -39,12 +41,14 @@ func (dca *resourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int,
func (dca *resourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (dca *resourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
set.(T).SetResourceModel(nil) set.(T).SetResourceModel(nil)
return utils.GenericUpdateOne(set.(T).Trim(), id, dca, dca.generateData()) // TODO CHANGE set.(T).Trim()
return utils.GenericUpdateOne(set, id, dca, dca.generateData())
} }
func (dca *resourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (dca *resourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
data.(T).SetResourceModel(nil) data.(T).SetResourceModel(nil)
return utils.GenericStoreOne(data.(T).Trim(), dca) data.(T).Trim()
return utils.GenericStoreOne(data, dca)
} }
func (dca *resourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (dca *resourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
@ -53,33 +57,36 @@ func (dca *resourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObjec
func (dca *resourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) { func (dca *resourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
resources, _, err := dca.ResourceModelAccessor.Search(nil, dca.GetType().String()) resources, _, err := dca.ResourceModelAccessor.Search(nil, dca.GetType().String(), false)
if err == nil && len(resources) > 0 { if err == nil && len(resources) > 0 {
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
} }
d.(T).SetAllowedInstances(dca.Request)
return d, 200, nil return d, 200, nil
}, dca) }, dca)
} }
func (wfa *resourceMongoAccessor[T]) LoadAll() ([]utils.ShallowDBObject, int, error) { func (wfa *resourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String()) resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String(), isDraft)
return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject {
if err == nil && len(resources) > 0 { if err == nil && len(resources) > 0 {
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
} }
d.(T).SetAllowedInstances(wfa.Request)
return d return d
}, wfa) }, isDraft, wfa)
} }
func (wfa *resourceMongoAccessor[T]) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (wfa *resourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String()) resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String(), false)
return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search), return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search),
func(d utils.DBObject) utils.ShallowDBObject { func(d utils.DBObject) utils.ShallowDBObject {
if err == nil && len(resources) > 0 { if err == nil && len(resources) > 0 {
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
} }
d.(T).SetAllowedInstances(wfa.Request)
return d return d
}, wfa) }, isDraft, wfa)
} }
func (abs *resourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters { func (abs *resourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters {

View File

@ -2,13 +2,9 @@ package resource_model
import ( import (
"encoding/json" "encoding/json"
"slices"
"cloud.o-forge.io/core/oc-lib/config"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
type WebResource struct { type WebResource struct {
@ -16,94 +12,6 @@ type WebResource struct {
Path string `bson:"path,omitempty" json:"path,omitempty"` // Path is the path of the URL Path string `bson:"path,omitempty" json:"path,omitempty"` // Path is the path of the URL
} }
/*
* AbstractResource is a struct that represents a resource
* it defines the resource data
*/
type AbstractResource struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource
Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
Owner string `json:"owner,omitempty" bson:"owner,omitempty" validate:"required"` // Owner is the owner of the resource
OwnerLogo string `json:"owner_logo,omitempty" bson:"owner_logo,omitempty"` // OwnerLogo is the owner logo of the resource
SourceUrl string `json:"source_url,omitempty" bson:"source_url,omitempty" validate:"required"` // SourceUrl is the source URL of the resource
PeerID string `json:"peer_id,omitempty" bson:"peer_id,omitempty" validate:"required"` // PeerID is the ID of the peer getting this resource
License string `json:"license,omitempty" bson:"license,omitempty"` // License is the license of the resource
ResourceModel *ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"` // ResourceModel is the model of the resource
AllowedPeersGroup map[string][]string `json:"allowed_peers_group,omitempty" bson:"allowed_peers_group,omitempty"` // AllowedPeersGroup is the group of allowed peers
Price string `json:"price,omitempty" bson:"price,omitempty"` // Price is the price of access to the resource
Currency string `json:"currency,omitempty" bson:"currency,omitempty"` // Currency is the currency of the price
}
func (ao *AbstractResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor {
return nil
}
func (abs *AbstractResource) SetResourceModel(model *ResourceModel) {
abs.ResourceModel = model
}
func (abs *AbstractResource) VerifyAuth(username string, peerID string, groups []string) bool {
if grps, ok := abs.AllowedPeersGroup[peerID]; ok || config.GetConfig().Whitelist {
if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
return true
}
for _, grp := range grps {
if slices.Contains(groups, grp) {
return true
}
}
}
return abs.AbstractObject.VerifyAuth(username, peerID, groups)
}
/*
* GetModelType returns the type of the model key
*/
func (abs *AbstractResource) GetModelType(cat string, key string) interface{} {
if abs.ResourceModel == nil || abs.ResourceModel.Model == nil {
return nil
}
if _, ok := abs.ResourceModel.Model[key]; !ok {
return nil
}
return abs.ResourceModel.Model[cat][key].Type
}
/*
* GetModelKeys returns the keys of the model
*/
func (abs *AbstractResource) GetModelKeys() []string {
keys := make([]string, 0)
for k := range abs.ResourceModel.Model {
keys = append(keys, k)
}
return keys
}
/*
* GetModelReadOnly returns the readonly of the model key
*/
func (abs *AbstractResource) GetModelReadOnly(cat string, key string) interface{} {
if abs.ResourceModel == nil || abs.ResourceModel.Model == nil {
return nil
}
if _, ok := abs.ResourceModel.Model[key]; !ok {
return nil
}
return abs.ResourceModel.Model[cat][key].ReadOnly
}
func (d *AbstractResource) Trim() *AbstractResource {
if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.PeerID}}).IsMySelf(); !ok {
d.AllowedPeersGroup = map[string][]string{}
}
return d
}
type Model struct { type Model struct {
Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the model Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the model
ReadOnly bool `json:"readonly,omitempty" bson:"readonly,omitempty"` // ReadOnly is the readonly of the model ReadOnly bool `json:"readonly,omitempty" bson:"readonly,omitempty"` // ReadOnly is the readonly of the model
@ -115,38 +23,26 @@ type Model struct {
* Warning: This struct is not user available, it is only used by the system * Warning: This struct is not user available, it is only used by the system
*/ */
type ResourceModel struct { type ResourceModel struct {
UUID string `json:"id,omitempty" bson:"id,omitempty" validate:"required"` utils.AbstractObject
ResourceType string `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` ResourceType string `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"`
VarRefs map[string]string `json:"var_refs,omitempty" bson:"var_refs,omitempty"` // VarRefs is the variable references of the model VarRefs map[string]string `json:"var_refs,omitempty" bson:"var_refs,omitempty"` // VarRefs is the variable references of the model
Model map[string]map[string]Model `json:"model,omitempty" bson:"model,omitempty"` Model map[string]map[string]Model `json:"model,omitempty" bson:"model,omitempty"`
} }
func (ao *ResourceModel) GetID() string { func (d *ResourceModel) StoreDraftDefault() {
return ao.UUID d.Name = d.ResourceType + " Resource Model"
d.IsDraft = false
} }
func (ao *ResourceModel) UpToDate(user string, create bool) {} func (abs *ResourceModel) VerifyAuth(request *tools.APIRequest) bool {
func (r *ResourceModel) GenerateID() {
r.UUID = uuid.New().String()
}
func (d *ResourceModel) GetName() string {
return d.UUID
}
func (abs *ResourceModel) VerifyAuth(username string, peerID string, groups []string) bool {
return true return true
} }
func (d *ResourceModel) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *ResourceModel) GetAccessor(request *tools.APIRequest) utils.Accessor {
return &ResourceModelMongoAccessor{ return &ResourceModelMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Type: tools.RESOURCE_MODEL, Type: tools.RESOURCE_MODEL,
PeerID: peerID, Request: request,
Groups: groups,
User: username,
Caller: caller,
}, },
} }
} }

View File

@ -15,7 +15,7 @@ type ResourceModelMongoAccessor struct {
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func New() *ResourceModelMongoAccessor { func NewAccessor() *ResourceModelMongoAccessor {
return &ResourceModelMongoAccessor{ return &ResourceModelMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Type: tools.RESOURCE_MODEL, Type: tools.RESOURCE_MODEL,
@ -46,17 +46,17 @@ func (a *ResourceModelMongoAccessor) LoadOne(id string) (utils.DBObject, int, er
}, a) }, a)
} }
func (a *ResourceModelMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *ResourceModelMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*ResourceModel](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[*ResourceModel](func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, a) }, isDraft, a)
} }
func (a *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*ResourceModel](filters, search, return utils.GenericSearch[*ResourceModel](filters, search,
&dbs.Filters{ &dbs.Filters{
Or: map[string][]dbs.Filter{ Or: map[string][]dbs.Filter{
"resource_type": {{Operator: dbs.LIKE.String(), Value: search}}, "resource_type": {{Operator: dbs.LIKE.String(), Value: search}},
}, },
}, func(d utils.DBObject) utils.ShallowDBObject { return d }, a) }, func(d utils.DBObject) utils.ShallowDBObject { return d }, isDraft, a)
} }

View File

@ -1,61 +1,137 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "errors"
"time"
"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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type StorageSize int
// StorageType - Enum that defines the type of storage
const (
GB StorageSize = iota
MB
KB
)
var argoType = [...]string{
"Gi",
"Mi",
"Ki",
}
// New creates a new instance of the StorageResource struct
func (dma StorageSize) ToArgo() string {
return argoType[dma]
}
// enum of a data type
type StorageType int
const (
FILE = iota
STREAM
API
DATABASE
S3
MEMORY
HARDWARE
)
/* /*
* StorageResource is a struct that represents a storage resource * StorageResource is a struct that represents a storage resource
* it defines the resource storage * it defines the resource storage
*/ */
type StorageResource struct { type StorageResource struct {
resource_model.AbstractResource // AbstractResource contains the basic fields of an object (id, name) AbstractResource[*StorageResourceInstance] // AbstractResource contains the basic fields of an object (id, name)
resource_model.WebResource Type common.StorageType `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage
Type StorageType `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage Acronym string `bson:"acronym,omitempty" json:"acronym,omitempty"` // Acronym is the acronym of the storage
Acronym string `bson:"acronym,omitempty" json:"acronym,omitempty"` // Acronym is the acronym of the storage
SizeType StorageSize `bson:"size_type" json:"size_type" default:"0"` // SizeType is the type of the storage size
Size uint `bson:"size,omitempty" json:"size,omitempty"` // Size is the size of the storage
Local bool `bson:"local" json:"local"` // Local is a flag that indicates if the storage is local
Encryption bool `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
Redundancy string `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
} }
func (d *StorageResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *StorageResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New[*StorageResource](tools.STORAGE_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor return NewAccessor[*StorageResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor
}
type StorageResourceInstance struct {
ResourceInstance[*StorageResourcePartnership]
Local bool `bson:"local" json:"local"`
SecurityLevel string `bson:"security_level,omitempty" json:"security_level,omitempty"`
SizeType common.StorageSize `bson:"size_type" json:"size_type" default:"0"` // SizeType is the type of the storage size
SizeGB uint `bson:"size,omitempty" json:"size,omitempty"` // Size is the size of the storage
Encryption bool `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
Redundancy string `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
}
func (i *StorageResourceInstance) GetID() string {
return i.UUID
}
type StorageResourcePartnership struct {
ResourcePartnerShip[*StorageResourcePricingProfile]
MaxSizeGBAllowed float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
OnlyEncryptedAllowed bool `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
}
type PrivilegeStoragePricingStrategy int
const (
BASIC_STORAGE PrivilegeStoragePricingStrategy = iota
GARANTED_ON_DELAY_STORAGE
GARANTED_STORAGE
)
func (t PrivilegeStoragePricingStrategy) String() string {
return [...]string{"BASIC_STORAGE", "GARANTED_ON_DELAY_STORAGE", "GARANTED_STORAGE"}[t]
}
type StorageResourcePricingStrategy int
const (
PER_DATA_STORED StorageResourcePricingStrategy = iota
PER_TB_STORED
PER_GB_STORED
PER_MB_STORED
PER_KB_STORED
)
func (t StorageResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DATA_STORED", "PER_GB_STORED", "PER_MB_STORED", "PER_KB_STORED"}[t]
}
func (t StorageResourcePricingStrategy) GetStrategyValue() int {
return int(t)
}
func ToStorageResourcePricingStrategy(i int) StorageResourcePricingStrategy {
return StorageResourcePricingStrategy(i)
}
func (t StorageResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
switch t {
case PER_DATA_STORED:
return amountOfDataGB, nil
case PER_TB_STORED:
return amountOfDataGB * 1000, nil
case PER_GB_STORED:
return amountOfDataGB, nil
case PER_MB_STORED:
return (amountOfDataGB * 1000), nil
case PER_KB_STORED:
return amountOfDataGB * 1000000, nil
}
return 0, errors.New("Pricing strategy not found")
}
type StorageResourcePricingProfile struct {
pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
}
func (p *StorageResourcePricingProfile) IsPurchased() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}
func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end)
}
type CustomizedStorageResource struct {
AbstractCustomizedResource[*StorageResourceInstance]
StorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
}
func (r *CustomizedStorageResource) GetType() tools.DataType {
return tools.STORAGE_RESOURCE
}
func (r *CustomizedStorageResource) GetPrice(request *tools.APIRequest) (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("Usage start and end must be set")
}
partner := r.GetPartnership(request)
if partner != nil && partner.GetPricing(r.SelectedPricing) != nil {
return 0, errors.New("Pricing strategy not found")
}
pricing := partner.GetPricing(r.SelectedPricing)
var err error
amountOfData := float64(1)
if pricing.GetOverrideStrategyValue() >= 0 {
amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.StorageGB)
if err != nil {
return 0, err
}
}
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request)
} }

View File

@ -1,18 +1,103 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "time"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
// WorkflowResource is a struct that represents a workflow resource // COMPLEX SHOULD BE REFACTORED
// it defines the resource workflow // we don't have any information about the accessor
type WorkflowResource struct { type abstractWorkflowResource struct {
resource_model.AbstractResource ExploitedResourceSet
WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow
} }
func (d *WorkflowResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { // WorkflowResource is a struct that represents a workflow resource
return New[*WorkflowResource](tools.WORKFLOW_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &WorkflowResource{} }) // Create a new instance of the accessor // it defines the resource workflow
type WorkflowResource struct {
AbstractResource[*ResourceInstance[*ResourcePartnerShip[*WorkflowResourcePricingProfile]]]
abstractWorkflowResource
}
type CustomizedWorkflowResource struct {
AbstractCustomizedResource[*ResourceInstance[*ResourcePartnerShip[*WorkflowResourcePricingProfile]]]
abstractWorkflowResource
}
func (r *CustomizedWorkflowResource) GetType() tools.DataType {
return tools.WORKFLOW_RESOURCE
}
func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) // Create a new instance of the accessor
}
type WorkflowResourcePricingProfile struct {
ID string `json:"id,omitempty" bson:"id,omitempty"`
ExploitedResourceSet
}
func (p *WorkflowResourcePricingProfile) GetOverrideStrategyValue() int {
return -1
}
func (p *WorkflowResourcePricingProfile) GetID() string {
return p.ID
}
func (p *WorkflowResourcePricingProfile) IsPurchased() bool {
return false
}
/*
* Missing wich Instance is selected
*
*/
func (p *WorkflowResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
// load workflow
price := float64(0)
pp, err := getPrice[*CustomizedDataResource](p.DataResources, amountOfData, val, start, end, request, params...)
if err != nil {
return 0, err
}
price += pp
pp, err = getPrice[*CustomizedStorageResource](p.StorageResources, amountOfData, val, start, end, request, params...)
if err != nil {
return 0, err
}
price += pp
pp, err = getPrice[*CustomizedProcessingResource](p.ProcessingResources, amountOfData, val, start, end, request, params...)
if err != nil {
return 0, err
}
price += pp
pp, err = getPrice[*CustomizedComputeResource](p.ComputeResources, amountOfData, val, start, end, request, params...)
if err != nil {
return 0, err
}
price += pp
pp, err = getPrice[*CustomizedWorkflowResource](p.WorkflowResources, amountOfData, val, start, end, request, params...)
if err != nil {
return 0, err
}
price += pp
return price, nil
}
func getPrice[T ShallowResourceInterface](arr []T, amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) {
// load workflow
price := float64(0)
for _, data := range arr {
partner := data.GetPartnership(request)
pricing := partner.GetPricing(data.GetPricingID())
pp, err := pricing.GetPrice(amountOfData, val, start, end, request)
if err != nil {
return 0, err
}
price += pp
}
return price, nil
} }

View File

@ -2,17 +2,13 @@ package utils
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/rs/zerolog" "github.com/rs/zerolog"
mgb "go.mongodb.org/mongo-driver/mongo"
) )
// single instance of the validator used in every model Struct to validate the fields // single instance of the validator used in every model Struct to validate the fields
@ -31,12 +27,15 @@ const (
* every data in base root model should inherit from this struct (only exception is the ResourceModel) * every data in base root model should inherit from this struct (only exception is the ResourceModel)
*/ */
type AbstractObject struct { type AbstractObject struct {
UUID string `json:"id,omitempty" bson:"id,omitempty" validate:"required"` UUID string `json:"id,omitempty" bson:"id,omitempty" validate:"required"`
Name string `json:"name,omitempty" bson:"name,omitempty" validate:"required"` Name string `json:"name,omitempty" bson:"name,omitempty" validate:"required"`
UpdateDate time.Time `json:"update_date" bson:"update_date"` IsDraft bool `json:"is_draft" bson:"is_draft" default:"false"`
LastPeerWriter string `json:"last_peer_writer" bson:"last_peer_writer"` CreatorID string `json:"creator_id" bson:"creator_id" default:"unknown"`
CreatorID string `json:"creator_id" bson:"creator_id" default:"unknown"`
AccessMode AccessMode `json:"access_mode" bson:"access_mode" default:"0"` CreationDate time.Time `json:"creation_date" bson:"creation_date"`
UpdateDate time.Time `json:"update_date" bson:"update_date"`
UpdaterID string `json:"updater_id" bson:"updater_id"`
AccessMode AccessMode `json:"access_mode" bson:"access_mode" default:"0"`
} }
func (r *AbstractObject) GenerateID() { func (r *AbstractObject) GenerateID() {
@ -45,6 +44,22 @@ func (r *AbstractObject) GenerateID() {
} }
} }
func (r *AbstractObject) StoreDraftDefault() {
r.IsDraft = false
}
func (r *AbstractObject) CanUpdate(set DBObject) (bool, DBObject) {
return true, set
}
func (r *AbstractObject) CanDelete() bool {
return true
}
func (r *AbstractObject) IsDrafted() bool {
return r.IsDraft
}
// GetID implements ShallowDBObject. // GetID implements ShallowDBObject.
func (ao AbstractObject) GetID() string { func (ao AbstractObject) GetID() string {
return ao.UUID return ao.UUID
@ -57,14 +72,15 @@ func (ao AbstractObject) GetName() string {
func (ao *AbstractObject) UpToDate(user string, create bool) { func (ao *AbstractObject) UpToDate(user string, create bool) {
ao.UpdateDate = time.Now() ao.UpdateDate = time.Now()
ao.LastPeerWriter = user ao.UpdaterID = user
if create { if create {
ao.CreationDate = time.Now()
ao.CreatorID = user ao.CreatorID = user
} }
} }
func (ao *AbstractObject) VerifyAuth(username string, peerID string, groups []string) bool { func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool {
return ao.AccessMode == Public || ao.CreatorID == username return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.Username)
} }
func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters { func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters {
@ -96,171 +112,43 @@ func (dma *AbstractObject) Serialize(obj DBObject) map[string]interface{} {
type AbstractAccessor struct { type AbstractAccessor struct {
Logger zerolog.Logger // Logger is the logger of the accessor, it's a specilized logger for the accessor Logger zerolog.Logger // Logger is the logger of the accessor, it's a specilized logger for the accessor
Type tools.DataType // Type is the data type of the accessor Type tools.DataType // Type is the data type of the accessor
Caller *tools.HTTPCaller // Caller is the http caller of the accessor (optionnal) only need in a peer connection Request *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection
PeerID string // PeerID is the id of the peer
Groups []string // Groups is the list of groups that can access the accessor
User string // User is the user that is using the accessor
ResourceModelAccessor Accessor ResourceModelAccessor Accessor
} }
func (r *AbstractAccessor) GetRequest() *tools.APIRequest {
return r.Request
}
func (dma *AbstractAccessor) GetUser() string { func (dma *AbstractAccessor) GetUser() string {
return dma.User if dma.Request == nil {
return ""
}
return dma.Request.Username
} }
func (dma *AbstractAccessor) GetPeerID() string { func (dma *AbstractAccessor) GetPeerID() string {
return dma.PeerID if dma.Request == nil {
return ""
}
return dma.Request.PeerID
} }
func (dma *AbstractAccessor) GetGroups() []string { func (dma *AbstractAccessor) GetGroups() []string {
return dma.Groups if dma.Request == nil {
return []string{}
}
return dma.Request.Groups
} }
func (dma *AbstractAccessor) GetLogger() *zerolog.Logger { func (dma *AbstractAccessor) GetLogger() *zerolog.Logger {
return &dma.Logger return &dma.Logger
} }
func (dma *AbstractAccessor) VerifyAuth() string {
return ""
}
func (dma *AbstractAccessor) GetType() tools.DataType { func (dma *AbstractAccessor) GetType() tools.DataType {
return dma.Type return dma.Type
} }
func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller { func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller {
return dma.Caller if dma.Request == nil {
} return nil
}
// GenericLoadOne loads one object from the database (generic) return dma.Request.Caller
func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
data.GenerateID()
data.UpToDate(a.GetUser(), true)
f := dbs.Filters{
Or: map[string][]dbs.Filter{
"abstractresource.abstractobject.name": {{
Operator: dbs.LIKE.String(),
Value: data.GetName(),
}},
"abstractobject.name": {{
Operator: dbs.LIKE.String(),
Value: data.GetName(),
}},
},
}
if !data.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) {
return nil, 403, errors.New("You are not allowed to access this collaborative area")
}
if cursor, _, _ := a.Search(&f, ""); len(cursor) > 0 {
return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists")
}
err := validate.Struct(data)
if err != nil {
return nil, 422, err
}
id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
}
// GenericLoadOne loads one object from the database (generic)
func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
res, code, err := a.LoadOne(id)
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve " + id + " to db. Error: " + err.Error())
return nil, code, err
}
if !res.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) {
return nil, 403, errors.New("You are not allowed to access this collaborative area")
}
_, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return res, 200, nil
}
// GenericLoadOne loads one object from the database (generic)
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) {
r, c, err := a.LoadOne(id)
if err != nil {
return nil, c, err
}
r.UpToDate(a.GetUser(), false)
if !r.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) {
return nil, 403, errors.New("You are not allowed to access this collaborative area")
}
change := set.Serialize(set) // get the changes
loaded := r.Serialize(r) // get the loaded object
for k, v := range change { // apply the changes, with a flatten method
loaded[k] = v
}
id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
}
func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) {
var data T
res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
return nil, code, err
}
res_mongo.Decode(&data)
if !data.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) {
return nil, 403, errors.New("You are not allowed to access this collaborative area")
}
return f(data)
}
func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) {
objs := []ShallowDBObject{}
var results []T
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve any from db. Error: " + err.Error())
return nil, code, err
}
if err = res.All(mongo.MngoCtx, &results); err != nil {
return nil, 404, err
}
for _, r := range results {
if !r.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) {
continue
}
objs = append(objs, f(r))
}
return objs, 200, nil
}
func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, wfa Accessor) ([]ShallowDBObject, int, error) {
res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String())
fmt.Println("res_mongo", res_mongo)
return genericLoadAll[T](res_mongo, code, err, f, wfa)
}
func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters,
f func(DBObject) ShallowDBObject, wfa Accessor) ([]ShallowDBObject, int, error) {
if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
filters = defaultFilters
}
res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String())
return genericLoadAll[T](res_mongo, code, err, f, wfa)
}
// GenericLoadOne loads one object from the database (generic)
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) {
id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
} }

View File

@ -1,8 +1,160 @@
package utils package utils
/* import (
type Price struct { "errors"
Price float64 `json:"price,omitempty" bson:"price,omitempty"` "fmt"
Currency string `json:"currency,omitempty" bson:"currency,omitempty"`
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo"
mgb "go.mongodb.org/mongo-driver/mongo"
)
type Owner struct {
Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
}
// GenericLoadOne loads one object from the database (generic)
func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
data.GenerateID()
data.StoreDraftDefault()
data.UpToDate(a.GetUser(), true)
f := dbs.Filters{
Or: map[string][]dbs.Filter{
"abstractresource.abstractobject.name": {{
Operator: dbs.LIKE.String(),
Value: data.GetName(),
}},
"abstractobject.name": {{
Operator: dbs.LIKE.String(),
Value: data.GetName(),
}},
},
}
if !data.VerifyAuth(a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access this collaborative area")
}
if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 {
return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists")
}
err := validate.Struct(data)
if err != nil {
return nil, 422, err
}
id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
}
// GenericLoadOne loads one object from the database (generic)
func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
res, code, err := a.LoadOne(id)
if !res.CanDelete() {
return nil, 403, errors.New("you are not allowed to delete this collaborative area")
}
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve " + id + " to db. Error: " + err.Error())
return nil, code, err
}
if !res.VerifyAuth(a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access this collaborative area")
}
_, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return res, 200, nil
}
// GenericLoadOne loads one object from the database (generic)
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) {
r, c, err := a.LoadOne(id)
if err != nil {
return nil, c, err
}
ok, newSet := r.CanUpdate(set)
if !ok {
return nil, 403, errors.New("you are not allowed to delete this collaborative area")
}
set = newSet
r.UpToDate(a.GetUser(), false)
if !r.VerifyAuth(a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access this collaborative area")
}
change := set.Serialize(set) // get the changes
loaded := r.Serialize(r) // get the loaded object
for k, v := range change { // apply the changes, with a flatten method
loaded[k] = v
}
id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
}
func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) {
var data T
res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
return nil, code, err
}
res_mongo.Decode(&data)
if !data.VerifyAuth(a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access this collaborative area")
}
return f(data)
}
func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft bool, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) {
objs := []ShallowDBObject{}
var results []T
if err != nil {
a.GetLogger().Error().Msg("Could not retrieve any from db. Error: " + err.Error())
return nil, code, err
}
if err = res.All(mongo.MngoCtx, &results); err != nil {
return nil, 404, err
}
for _, r := range results {
if !r.VerifyAuth(a.GetRequest()) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) {
continue
}
objs = append(objs, f(r))
}
return objs, 200, nil
}
func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String())
fmt.Println("res_mongo", res_mongo)
return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
}
func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters,
f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
filters = defaultFilters
}
res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String())
return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
}
// GenericLoadOne loads one object from the database (generic)
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) {
id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String())
if err != nil {
a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
return nil, code, err
}
return a.LoadOne(id)
} }
*/

View File

@ -20,23 +20,28 @@ type DBObject interface {
GenerateID() GenerateID()
GetID() string GetID() string
GetName() string GetName() string
IsDrafted() bool
StoreDraftDefault()
CanUpdate(set DBObject) (bool, DBObject)
CanDelete() bool
UpToDate(user string, create bool) UpToDate(user string, create bool)
VerifyAuth(username string, PeerID string, groups []string) bool VerifyAuth(request *tools.APIRequest) bool
Deserialize(j map[string]interface{}, obj DBObject) DBObject Deserialize(j map[string]interface{}, obj DBObject) DBObject
Serialize(obj DBObject) map[string]interface{} Serialize(obj DBObject) map[string]interface{}
GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) Accessor GetAccessor(request *tools.APIRequest) Accessor
} }
// Accessor is an interface that defines the basic methods for an Accessor // Accessor is an interface that defines the basic methods for an Accessor
type Accessor interface { type Accessor interface {
GetRequest() *tools.APIRequest
GetType() tools.DataType GetType() tools.DataType
GetUser() string GetUser() string
GetPeerID() string GetPeerID() string
GetGroups() []string GetGroups() []string
GetLogger() *zerolog.Logger GetLogger() *zerolog.Logger
GetCaller() *tools.HTTPCaller GetCaller() *tools.HTTPCaller
Search(filters *dbs.Filters, search string) ([]ShallowDBObject, int, error) Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error)
LoadAll() ([]ShallowDBObject, int, error) LoadAll(isDraft bool) ([]ShallowDBObject, int, error)
LoadOne(id string) (DBObject, int, error) LoadOne(id string) (DBObject, int, error)
DeleteOne(id string) (DBObject, int, error) DeleteOne(id string) (DBObject, int, error)
CopyOne(data DBObject) (DBObject, int, error) CopyOne(data DBObject) (DBObject, int, error)

View File

@ -1,8 +1,10 @@
package graph package graph
import ( import (
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources" "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/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -13,7 +15,84 @@ type Graph struct {
Links []GraphLink `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph Links []GraphLink `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
} }
func (g *Graph) GetResource(id string) (string, utils.DBObject) { func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.CustomizedProcessingResource, resource resources.ShallowResourceInterface, f func(GraphItem) resources.ShallowResourceInterface) (float64, float64) {
nearestStart := float64(10000000000)
oneIsInfinite := false
longestDuration := float64(0)
for _, link := range g.Links {
for _, processing := range processings {
var source string // source is the source of the link
if link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the destination is the processing and the source is not a compute
source = link.Source.ID
} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
source = link.Destination.ID
}
if source != "" {
if processing.UsageStart != nil {
near := float64(processing.UsageStart.Sub(start).Seconds())
if near < nearestStart {
nearestStart = near
}
}
if processing.UsageEnd != nil {
duration := float64(processing.UsageEnd.Sub(*processing.UsageStart).Seconds())
if longestDuration < duration {
longestDuration = duration
}
} else {
oneIsInfinite = true
}
}
}
}
if oneIsInfinite {
return nearestStart, -1
}
return nearestStart, longestDuration
}
func (g *Graph) SetItemStartUsage(graphItemID string, start time.Time) {
g.Items[graphItemID].SetItemStartUsage(start)
}
func (g *Graph) SetItemEndUsage(graphItemID string, end time.Time) {
g.Items[graphItemID].SetItemEndUsage(end)
}
/*
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
*/
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string) float64 {
currents := []float64{} // list of current time
for _, link := range g.Links { // for each link
var source string // source is the source of the link
if link.Destination.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the destination is the processing and the source is not a compute
source = link.Source.ID
} else if link.Source.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the source is the processing and the destination is not a compute
source = link.Destination.ID
}
if source == "" { // if source is empty, continue
continue
}
_, item := g.GetResource(source) // get the resource of the source
current := item.GetExplicitDurationInS() // get the explicit duration of the item
if current < 0 { // if current is negative, its means that duration of a before could be infinite continue
return current
}
current += g.GetAverageTimeProcessingBeforeStart(current, source) // get the average time before start of the source
currents = append(currents, current) // append the current to the currents
}
var max float64 // get the max time to wait dependancies to finish
for _, current := range currents {
if current > max {
max = current
}
}
return max
}
func (g *Graph) GetResource(id string) (string, resources.ShallowResourceInterface) {
if item, ok := g.Items[id]; ok { if item, ok := g.Items[id]; ok {
if item.Data != nil { if item.Data != nil {
return tools.DATA_RESOURCE.String(), item.Data return tools.DATA_RESOURCE.String(), item.Data
@ -32,11 +111,41 @@ func (g *Graph) GetResource(id string) (string, utils.DBObject) {
// GraphItem is a struct that represents an item in a graph // GraphItem is a struct that represents an item in a graph
type GraphItem struct { type GraphItem struct {
ID string `bson:"id" json:"id" validate:"required"` // ID is the unique identifier of the item ID string `bson:"id" json:"id" validate:"required"` // ID is the unique identifier of the item
Width float64 `bson:"width" json:"width" validate:"required"` // Width is the graphical width of the item Width float64 `bson:"width" json:"width" validate:"required"` // Width is the graphical width of the item
Height float64 `bson:"height" json:"height" validate:"required"` // Height is the graphical height of the item Height float64 `bson:"height" json:"height" validate:"required"` // Height is the graphical height of the item
Position Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item Position Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item
*resources.ItemResource // ItemResource is the resource of the item affected to the item *resources.ItemExploitedResource // ItemResource is the resource of the item affected to the item
}
func (g *GraphItem) GetResource() resources.ShallowResourceInterface {
if g.Data != nil {
return g.Data
} else if g.Compute != nil {
return g.Compute
} else if g.Workflow != nil {
return g.Workflow
} else if g.Processing != nil {
return g.Processing
} else if g.Storage != nil {
return g.Storage
}
return nil
}
func (g *GraphItem) GetPricedItem() pricing.PricedItemITF {
if g.Data != nil {
return g.Data
} else if g.Compute != nil {
return g.Compute
} else if g.Workflow != nil {
return g.Workflow
} else if g.Processing != nil {
return g.Processing
} else if g.Storage != nil {
return g.Storage
}
return nil
} }
// GraphLink is a struct that represents a link between two items in a graph // GraphLink is a struct that represents a link between two items in a graph
@ -46,6 +155,20 @@ type GraphLink struct {
Style *GraphLinkStyle `bson:"style,omitempty" json:"style,omitempty"` // Style is the graphical style of the link Style *GraphLinkStyle `bson:"style,omitempty" json:"style,omitempty"` // Style is the graphical style of the link
} }
// tool function to check if a link is a link between a compute and a resource
func (l *GraphLink) IsComputeLink(g Graph) (bool, string) {
if g.Items == nil {
return false, ""
}
if d, ok := g.Items[l.Source.ID]; ok && d.Compute != nil {
return true, d.Compute.UUID
}
if d, ok := g.Items[l.Destination.ID]; ok && d.Compute != nil {
return true, d.Compute.UUID
}
return false, ""
}
// GraphLinkStyle is a struct that represents the style of a link in a graph // GraphLinkStyle is a struct that represents the style of a link in a graph
type GraphLinkStyle struct { type GraphLinkStyle struct {
Color int64 `bson:"color" json:"color"` // Color is the graphical color of the link (int description of a color, can be transpose as hex) Color int64 `bson:"color" json:"color"` // Color is the graphical color of the link (int description of a color, can be transpose as hex)
@ -64,7 +187,7 @@ type GraphLinkStyle struct {
// Position is a struct that represents a graphical position // Position is a struct that represents a graphical position
type Position struct { type Position struct {
ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal), TODO: rename to ItemID ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal)
X float64 `json:"x" bson:"x" validate:"required"` // X is the graphical x position X float64 `json:"x" bson:"x" validate:"required"` // X is the graphical x position
Y float64 `json:"y" bson:"y" validate:"required"` // Y is the graphical y position Y float64 `json:"y" bson:"y" validate:"required"` // Y is the graphical y position
} }

View File

@ -2,8 +2,10 @@ package workflow
import ( import (
"errors" "errors"
"time"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
"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/peer"
"cloud.o-forge.io/core/oc-lib/models/resources" "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/utils"
@ -19,29 +21,61 @@ import (
*/ */
type AbstractWorkflow struct { type AbstractWorkflow struct {
resources.ResourceSet resources.ResourceSet
Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow 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 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 // 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 Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow
} }
func (w *AbstractWorkflow) GetWorkflows() (list_computings []graph.GraphItem) { func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}
func (w *AbstractWorkflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) {
for _, item := range w.Graph.Items { for _, item := range w.Graph.Items {
if item.Workflow != nil { if f(item) {
list_computings = append(list_computings, item) list_datas = append(list_datas, item)
} }
} }
return return
} }
func (w *AbstractWorkflow) GetComputeByRelatedProcessing(processingID string) []*resources.ComputeResource { func (w *AbstractWorkflow) GetResources(f func(item graph.GraphItem) bool) map[string]resources.ShallowResourceInterface {
storages := []*resources.ComputeResource{} list_datas := map[string]resources.ShallowResourceInterface{}
for _, item := range w.Graph.Items {
if f(item) {
res := item.GetResource()
list_datas[res.GetID()] = res
}
}
return list_datas
}
func (w *AbstractWorkflow) GetPricedItem(f func(item graph.GraphItem) bool) map[string]pricing.PricedItemITF {
list_datas := map[string]pricing.PricedItemITF{}
for _, item := range w.Graph.Items {
if f(item) {
res := item.GetResource()
ord := item.GetPricedItem()
list_datas[res.GetID()] = ord
}
}
return list_datas
}
func (w *AbstractWorkflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) []resources.ShallowResourceInterface {
storages := []resources.ShallowResourceInterface{}
for _, link := range w.Graph.Links { for _, link := range w.Graph.Links {
nodeID := link.Destination.ID // we considers that the processing is the destination nodeID := link.Destination.ID
node := w.Graph.Items[link.Source.ID].Compute // we are looking for the storage as source var node resources.ShallowResourceInterface
if node == nil { // if the source is not a storage, we consider that the destination is the storage if g(w.Graph.Items[link.Source.ID]) {
nodeID = link.Source.ID // and the processing is the source item := w.Graph.Items[link.Source.ID]
node = w.Graph.Items[link.Destination.ID].Compute // we are looking for the storage as destination 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 if processingID == nodeID && node != nil { // if the storage is linked to the processing
storages = append(storages, node) storages = append(storages, node)
@ -50,43 +84,24 @@ func (w *AbstractWorkflow) GetComputeByRelatedProcessing(processingID string) []
return storages return storages
} }
func (w *AbstractWorkflow) GetStoragesByRelatedProcessing(processingID string) []*resources.StorageResource { func (wf *AbstractWorkflow) IsProcessing(item graph.GraphItem) bool {
storages := []*resources.StorageResource{} return item.Processing != nil
for _, link := range w.Graph.Links {
nodeID := link.Destination.ID // we considers that the processing is the destination
node := w.Graph.Items[link.Source.ID].Storage // we are looking for the storage as source
if node == nil { // if the source is not a storage, we consider that the destination is the storage
nodeID = link.Source.ID // and the processing is the source
node = w.Graph.Items[link.Destination.ID].Storage // 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 (w *AbstractWorkflow) GetProcessings() (list_computings []graph.GraphItem) { func (wf *AbstractWorkflow) IsCompute(item graph.GraphItem) bool {
for _, item := range w.Graph.Items { return item.Compute != nil
if item.Processing != nil {
list_computings = append(list_computings, item)
}
}
return
} }
// tool function to check if a link is a link between a compute and a resource func (wf *AbstractWorkflow) IsData(item graph.GraphItem) bool {
func (w *AbstractWorkflow) isDCLink(link graph.GraphLink) (bool, string) { return item.Data != nil
if w.Graph == nil || w.Graph.Items == nil { }
return false, ""
} func (wf *AbstractWorkflow) IsStorage(item graph.GraphItem) bool {
if d, ok := w.Graph.Items[link.Source.ID]; ok && d.Compute != nil { return item.Storage != nil
return true, d.Compute.UUID }
}
if d, ok := w.Graph.Items[link.Destination.ID]; ok && d.Compute != nil { func (wf *AbstractWorkflow) IsWorkflow(item graph.GraphItem) bool {
return true, d.Compute.UUID return item.Workflow != nil
}
return false, ""
} }
/* /*
@ -98,18 +113,51 @@ type Workflow struct {
AbstractWorkflow // AbstractWorkflow contains the basic fields of a workflow AbstractWorkflow // AbstractWorkflow contains the basic fields of a workflow
} }
func (ao *Workflow) VerifyAuth(username string, peerID string, groups []string) bool { func (w *Workflow) GetNearestStart(start time.Time) float64 {
near := float64(10000000000)
for _, item := range w.Graph.Items {
if item.GetResource().GetLocationStart() == nil {
continue
}
newS := item.GetResource().GetLocationStart()
if newS.Sub(start).Seconds() < near {
near = newS.Sub(start).Seconds()
}
// get the nearest start from start var
}
return near
}
func (w *Workflow) GetLongestTime(end *time.Time) float64 {
if end == nil {
return -1
}
longestTime := float64(0)
for _, item := range w.GetGraphItems(w.IsProcessing) {
if item.GetResource().GetLocationEnd() == nil {
continue
}
newS := item.GetResource().GetLocationEnd()
if longestTime < newS.Sub(*end).Seconds() {
longestTime = newS.Sub(*end).Seconds()
}
// get the nearest start from start var
}
return longestTime
}
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
isAuthorized := false isAuthorized := false
if len(ao.Shared) > 0 { if len(ao.Shared) > 0 {
for _, shared := range ao.Shared { for _, shared := range ao.Shared {
shared, code, _ := shallow_collaborative_area.New(tools.COLLABORATIVE_AREA, username, peerID, groups, nil).LoadOne(shared) shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(shared)
if code != 200 || shared == nil { if code != 200 || shared == nil {
isAuthorized = false isAuthorized = false
} }
isAuthorized = shared.VerifyAuth(username, peerID, groups) isAuthorized = shared.VerifyAuth(request)
} }
} }
return ao.AbstractObject.VerifyAuth(username, peerID, groups) || isAuthorized return ao.AbstractObject.VerifyAuth(request) || isAuthorized
} }
/* /*
@ -120,19 +168,19 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
if wfa.Graph == nil { // no graph no booking if wfa.Graph == nil { // no graph no booking
return false, nil return false, nil
} }
accessor := (&resources.ComputeResource{}).GetAccessor("", "", []string{}, caller) accessor := (&resources.ComputeResource{}).GetAccessor(&tools.APIRequest{Caller: caller})
for _, link := range wfa.Graph.Links { for _, link := range wfa.Graph.Links {
if ok, dc_id := wfa.isDCLink(link); ok { // check if the link is a link between a compute and a resource if ok, compute_id := link.IsComputeLink(*wfa.Graph); ok { // check if the link is a link between a compute and a resource
dc, code, _ := accessor.LoadOne(dc_id) compute, code, _ := accessor.LoadOne(compute_id)
if code != 200 { if code != 200 {
continue continue
} }
// CHECK BOOKING ON PEER, compute could be a remote one // CHECK BOOKING ON PEER, compute could be a remote one
peerID := dc.(*resources.ComputeResource).PeerID peerID := compute.(*resources.ComputeResource).CreatorID
if peerID == "" { if peerID == "" {
return false, errors.New("no peer id") return false, errors.New("no peer id")
} // no peer id no booking, we need to know where to book } // no peer id no booking, we need to know where to book
_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, dc_id, tools.BOOKING, tools.GET, nil, caller) _, err := (&peer.Peer{}).LaunchPeerExecution(peerID, compute_id, tools.BOOKING, tools.GET, nil, caller)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -140,7 +188,3 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
} }
return true, nil return true, nil
} }
func (d *Workflow) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor {
return New(tools.WORKFLOW, username, peerID, groups, caller) // Create a new instance of the accessor
}

View File

@ -8,8 +8,8 @@ import (
type WorkflowHistory struct{ Workflow } type WorkflowHistory struct{ Workflow }
func (d *WorkflowHistory) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *WorkflowHistory) GetAccessor(request tools.APIRequest) utils.Accessor {
return New(tools.WORKSPACE_HISTORY, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessorHistory(request) // Create a new instance of the accessor
} }
func (r *WorkflowHistory) GenerateID() { func (r *WorkflowHistory) GenerateID() {
r.UUID = uuid.New().String() r.UUID = uuid.New().String()

View File

@ -1,183 +1,55 @@
package workflow package workflow
import ( import (
"errors"
"fmt"
"slices"
"strings"
"time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
"cloud.o-forge.io/core/oc-lib/models/peer" "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/resources"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/models/workspace" "cloud.o-forge.io/core/oc-lib/models/workspace"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
cron "github.com/robfig/cron"
) )
type workflowMongoAccessor struct { type workflowMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
computeResourceAccessor utils.Accessor computeResourceAccessor utils.Accessor
collaborativeAreaAccessor utils.Accessor collaborativeAreaAccessor utils.Accessor
executionAccessor utils.Accessor
workspaceAccessor utils.Accessor workspaceAccessor utils.Accessor
} }
func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor {
return new(tools.WORKFLOW_HISTORY, request)
}
func NewAccessor(request *tools.APIRequest) *workflowMongoAccessor {
return new(tools.WORKFLOW, request)
}
// New creates a new instance of the workflowMongoAccessor // New creates a new instance of the workflowMongoAccessor
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workflowMongoAccessor { func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor {
return &workflowMongoAccessor{ return &workflowMongoAccessor{
computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(username, peerID, groups, nil), computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(request),
collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(username, peerID, groups, nil), collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request),
executionAccessor: (&workflow_execution.WorkflowExecution{}).GetAccessor(username, peerID, groups, nil), workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
workspaceAccessor: (&workspace.Workspace{}).GetAccessor(username, peerID, groups, nil),
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: t,
User: username,
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
/*
* THERE IS A LOT IN THIS FILE SHOULD BE AWARE OF THE COMMENTS
*/
/*
* getExecutions is a function that returns the executions of a workflow
* it returns an array of workflow_execution.WorkflowExecution
*/
func (a *workflowMongoAccessor) getExecutions(id string, data *Workflow) ([]*workflow_execution.WorkflowExecution, error) {
workflows_execution := []*workflow_execution.WorkflowExecution{}
if data.Schedule != nil { // only set execution on a scheduled workflow
if data.Schedule.Start == nil { // if no start date, return an error
return workflows_execution, errors.New("should get a start date on the scheduler.")
}
if data.Schedule.End != nil && data.Schedule.End.IsZero() { // if end date is zero, set it to nil
data.Schedule.End = nil
}
if len(data.Schedule.Cron) > 0 { // if cron is set then end date should be set
if data.Schedule.End == nil {
return workflows_execution, errors.New("a cron task should have an end date.")
}
cronStr := strings.Split(data.Schedule.Cron, " ") // split the cron string to treat it
if len(cronStr) < 6 { // if the cron string is less than 6 fields, return an error because format is : ss mm hh dd MM dw (6 fields)
return nil, errors.New("Bad cron message: " + data.Schedule.Cron + ". Should be at least ss mm hh dd MM dw")
}
subCron := strings.Join(cronStr[:6], " ")
// cron should be parsed as ss mm hh dd MM dw t (min 6 fields)
specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) // create a new cron parser
sched, err := specParser.Parse(subCron) // parse the cron string
if err != nil {
return workflows_execution, errors.New("Bad cron message: " + err.Error())
}
// loop through the cron schedule to set the executions
for s := sched.Next(*data.Schedule.Start); !s.IsZero() && s.Before(*data.Schedule.End); s = sched.Next(s) {
obj := &workflow_execution.WorkflowExecution{
AbstractObject: utils.AbstractObject{
Name: data.Schedule.Name, // set the name of the execution
},
ExecDate: &s, // set the execution date
EndDate: data.Schedule.End, // set the end date
State: 1, // set the state to 1 (scheduled)
WorkflowID: id, // set the workflow id dependancy of the execution
}
workflows_execution = append(workflows_execution, obj) // append the execution to the array
}
} else { // if no cron, set the execution to the start date
obj := &workflow_execution.WorkflowExecution{ // create a new execution
AbstractObject: utils.AbstractObject{
Name: data.Schedule.Name,
},
ExecDate: data.Schedule.Start,
EndDate: data.Schedule.End,
State: 1,
WorkflowID: id,
}
workflows_execution = append(workflows_execution, obj) // append the execution to the array
}
}
return workflows_execution, nil
}
// DeleteOne deletes a workflow from the database, delete depending executions and bookings // DeleteOne deletes a workflow from the database, delete depending executions and bookings
func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
a.execution(id, &Workflow{
AbstractWorkflow: AbstractWorkflow{ScheduleActive: false},
}, true) // delete the executions
res, code, err := utils.GenericDeleteOne(id, a) res, code, err := utils.GenericDeleteOne(id, a)
if res != nil && code == 200 { if res != nil && code == 200 {
a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow
a.share(res.(*Workflow), true, a.Caller) a.share(res.(*Workflow), true, a.GetCaller())
} }
return res, code, err return res, code, err
} }
/*
* book is a function that books a workflow on the peers
* it takes the workflow id, the real data and the executions
* it returns an error if the booking fails
*/
func (a *workflowMongoAccessor) book(id string, realData *Workflow, execs []*workflow_execution.WorkflowExecution) error {
if a.Caller == nil || a.Caller.URLS == nil || a.Caller.URLS[tools.BOOKING] == nil {
return errors.New("no caller defined")
}
methods := a.Caller.URLS[tools.BOOKING]
if _, ok := methods[tools.POST]; !ok {
return errors.New("no path found")
}
res, code, _ := a.LoadOne(id)
if code != 200 {
return errors.New("could not load workflow")
}
r := res.(*Workflow)
g := r.Graph
if realData.Graph != nil { // if the graph is set, set it to the real data
g = realData.Graph
}
if g != nil && g.Links != nil && len(g.Links) > 0 { // if the graph is set and has links then book the workflow (even on ourselves)
isDCFound := []string{}
for _, link := range g.Links {
if ok, dc_id := realData.isDCLink(link); ok { // check if the link is a link between a compute and a resource booking is only on compute
if slices.Contains(isDCFound, dc_id) {
continue
} // if the compute is already found, skip it
isDCFound = append(isDCFound, dc_id)
dc, code, _ := a.computeResourceAccessor.LoadOne(dc_id)
if code != 200 {
continue
}
// CHECK BOOKING
peerID := dc.(*resources.ComputeResource).PeerID
if peerID == "" { // no peer id no booking
continue
}
// BOOKING ON PEER
_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, "", tools.BOOKING, tools.POST,
(&workflow_execution.WorkflowExecutions{ // it's the standard model for booking see OC-PEER
WorkflowID: id, // set the workflow id "WHO"
ResourceID: dc_id, // set the compute id "WHERE"
Executions: execs, // set the executions to book "WHAT"
}).Serialize(), a.Caller)
if err != nil {
fmt.Println("BOOKING", err)
return err
}
}
}
}
return nil
}
/* /*
* share is a function that shares a workflow to the peers if the workflow is shared * share is a function that shares a workflow to the peers if the workflow is shared
*/ */
@ -197,10 +69,12 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t
if ok, _ := paccess.IsMySelf(); ok { // if the peer is the current peer, never share because it will create a loop if ok, _ := paccess.IsMySelf(); ok { // if the peer is the current peer, never share because it will create a loop
continue continue
} }
if delete { // if the workflow is deleted, share the deletion if delete { // if the workflow is deleted, share the deletion orderResourceAccessor utils.Accessor
history := NewHistory() history := NewHistory()
history.StoreOne(history.MapFromWorkflow(res.(*Workflow))) history.StoreOne(history.MapFromWorkflow(res.(*Workflow)))
_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE, map[string]interface{}{}, caller) _, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE,
map[string]interface{}{}, caller)
} else { // if the workflow is updated, share the update } else { // if the workflow is updated, share the update
_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT, _, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT,
res.Serialize(res), caller) res.Serialize(res), caller)
@ -212,99 +86,30 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t
} }
} }
/*
* execution is a create or delete function for the workflow executions depending on the schedule of the workflow
*/
func (a *workflowMongoAccessor) execution(id string, realData *Workflow, delete bool) (int, error) {
nats := tools.NewNATSCaller() // create a new nats caller because executions are sent to the nats for daemons
mongo.MONGOService.DeleteMultiple(map[string]interface{}{
"state": 1, // only delete the scheduled executions only scheduled if executions are in progress or ended, they should not be deleted for registration
"workflow_id": id,
}, tools.WORKFLOW_EXECUTION.String())
err := a.book(id, realData, []*workflow_execution.WorkflowExecution{}) // delete the booking of the workflow on the peers
fmt.Println("DELETE BOOKING", err)
nats.SetNATSPub(tools.WORKFLOW.String(), tools.REMOVE, realData) // send the deletion to the nats
if err != nil {
return 409, err
}
execs, err := a.getExecutions(id, realData) // get the executions of the workflow
if err != nil {
return 422, err
}
if !realData.ScheduleActive || delete { // if the schedule is not active, delete the executions
execs = []*workflow_execution.WorkflowExecution{}
}
err = a.book(id, realData, execs) // book the workflow on the peers
fmt.Println("BOOKING", err)
if err != nil {
return 409, err // if the booking fails, return an error for integrity between peers
}
fmt.Println("BOOKING", delete)
for _, obj := range execs {
_, code, err := a.executionAccessor.StoreOne(obj)
fmt.Println("EXEC", code, err)
if code != 200 {
return code, err
}
}
nats.SetNATSPub(tools.WORKFLOW.String(), tools.CREATE, realData) // send the creation to the nats
return 200, nil
}
// UpdateOne updates a workflow in the database // UpdateOne updates a workflow in the database
func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
res, code, err := a.LoadOne(id)
if code != 200 {
return nil, 409, err
}
// avoid the update if the schedule is the same // avoid the update if the schedule is the same
avoid := set.(*Workflow).Schedule == nil || (res.(*Workflow).Schedule != nil && res.(*Workflow).ScheduleActive == set.(*Workflow).ScheduleActive && res.(*Workflow).Schedule.Start == set.(*Workflow).Schedule.Start && res.(*Workflow).Schedule.End == set.(*Workflow).Schedule.End && res.(*Workflow).Schedule.Cron == set.(*Workflow).Schedule.Cron) res, code, err := utils.GenericUpdateOne(set, id, a, &Workflow{})
res, code, err = utils.GenericUpdateOne(set, id, a, &Workflow{})
if code != 200 { if code != 200 {
return nil, code, err return nil, code, err
} }
workflow := res.(*Workflow) workflow := res.(*Workflow)
if !avoid { // if the schedule is not avoided, update the executions a.execute(workflow, false, false) // update the workspace for the workflow
if code, err := a.execution(id, workflow, false); code != 200 { a.share(workflow, false, a.GetCaller()) // share the update to the peers
return nil, code, errors.New("could not update the executions : " + err.Error())
}
}
fmt.Println("UPDATE", workflow.ScheduleActive, workflow.Schedule)
if workflow.ScheduleActive && workflow.Schedule != nil { // if the workflow is scheduled, update the executions
now := time.Now().UTC()
if (workflow.Schedule.End != nil && now.After(*workflow.Schedule.End)) || (workflow.Schedule.End == nil && workflow.Schedule.Start != nil && now.After(*workflow.Schedule.Start)) { // if the start date is passed, then you can book
workflow.ScheduleActive = false
utils.GenericRawUpdateOne(workflow, id, a)
} // if the start date is passed, update the executions
}
a.execute(workflow, false, false) // update the workspace for the workflow
a.share(workflow, false, a.Caller) // share the update to the peers
return res, code, nil return res, code, nil
} }
// StoreOne stores a workflow in the database // StoreOne stores a workflow in the database
func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
d := data.(*Workflow) d := data.(*Workflow)
if d.ScheduleActive && d.Schedule != nil { // if the workflow is scheduled, update the executions
now := time.Now().UTC()
if (d.Schedule.End != nil && now.After(*d.Schedule.End)) || (d.Schedule.End == nil && d.Schedule.Start != nil && now.After(*d.Schedule.Start)) { // if the start date is passed, then you can book
d.ScheduleActive = false
} // if the start date is passed, update the executions
}
res, code, err := utils.GenericStoreOne(d, a) res, code, err := utils.GenericStoreOne(d, a)
if err != nil || code != 200 { if err != nil || code != 200 {
return nil, code, err return nil, code, err
} }
workflow := res.(*Workflow) workflow := res.(*Workflow)
a.share(workflow, false, a.Caller) // share the creation to the peers a.share(workflow, false, a.GetCaller()) // share the creation to the peers
//store the executions a.execute(workflow, false, false) // store the workspace for the workflow
if code, err := a.execution(res.GetID(), workflow, false); err != nil {
return nil, code, err
}
a.execute(workflow, false, false) // store the workspace for the workflow
return res, code, nil return res, code, nil
} }
@ -322,7 +127,7 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}},
}, },
} }
resource, _, err := a.workspaceAccessor.Search(filters, "") resource, _, err := a.workspaceAccessor.Search(filters, "", workflow.IsDraft)
if delete { // if delete is set to true, delete the workspace if delete { // if delete is set to true, delete the workspace
for _, r := range resource { for _, r := range resource {
a.workspaceAccessor.DeleteOne(r.GetID()) a.workspaceAccessor.DeleteOne(r.GetID())
@ -358,22 +163,15 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active
func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) {
w := d.(*Workflow) w := d.(*Workflow)
if w.ScheduleActive && w.Schedule != nil { // if the workflow is scheduled, update the executions
now := time.Now().UTC()
if (w.Schedule.End != nil && now.After(*w.Schedule.End)) || (w.Schedule.End == nil && w.Schedule.Start != nil && now.After(*w.Schedule.Start)) { // if the start date is passed, then you can book
w.ScheduleActive = false
utils.GenericRawUpdateOne(d, id, a)
} // if the start date is passed, update the executions
}
a.execute(w, false, true) // if no workspace is attached to the workflow, create it a.execute(w, false, true) // if no workspace is attached to the workflow, create it
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *workflowMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, a) return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a)
} }
func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return d }, a) return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return d }, isDraft, a)
} }

View File

@ -1,23 +0,0 @@
package workflow
import "time"
// WorkflowSchedule is a struct that contains the scheduling information of a workflow
type ScheduleMode int
const (
TASK ScheduleMode = iota
SERVICE
)
/*
* WorkflowSchedule is a struct that contains the scheduling information of a workflow
* It contains the mode of the schedule (Task or Service), the name of the schedule, the start and end time of the schedule and the cron expression
*/
type WorkflowSchedule struct {
Mode int64 `json:"mode" bson:"mode" validate:"required"` // Mode is the mode of the schedule (Task or Service)
Name string `json:"name" bson:"name" validate:"required"` // Name is the name of the schedule
Start *time.Time `json:"start" bson:"start" validate:"required,ltfield=End"` // Start is the start time of the schedule, is required and must be less than the End time
End *time.Time `json:"end,omitempty" bson:"end,omitempty"` // End is the end time of the schedule
Cron string `json:"cron,omitempty" bson:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task
}

View File

@ -4,7 +4,6 @@ import (
"testing" "testing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -13,7 +12,7 @@ func TestStoreOneWorkflow(t *testing.T) {
AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
} }
wma := New(tools.WORKFLOW, "", "", nil, nil) wma := NewAccessor(nil)
id, _, _ := wma.StoreOne(&w) id, _, _ := wma.StoreOne(&w)
assert.NotEmpty(t, id) assert.NotEmpty(t, id)
@ -24,7 +23,7 @@ func TestLoadOneWorkflow(t *testing.T) {
AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
} }
wma := New(tools.WORKFLOW, "", "", nil, nil) wma := NewAccessor(nil)
new_w, _, _ := wma.StoreOne(&w) new_w, _, _ := wma.StoreOne(&w)
assert.Equal(t, w, new_w) assert.Equal(t, w, new_w)
} }

View File

@ -1,124 +1,136 @@
package workflow_execution package workflow_execution
import ( import (
"encoding/json"
"strings" "strings"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/common"
"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/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid" "github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson/primitive"
) )
// ScheduledType - Enum for the different states of a workflow execution
type ScheduledType int
const (
SCHEDULED ScheduledType = iota + 1
STARTED
FAILURE
SUCCESS
FORGOTTEN
)
var str = [...]string{
"scheduled",
"started",
"failure",
"success",
"forgotten",
}
func FromInt(i int) string {
return str[i]
}
func (d ScheduledType) String() string {
return str[d]
}
// EnumIndex - Creating common behavior-give the type a EnumIndex functio
func (d ScheduledType) EnumIndex() int {
return int(d)
}
/* /*
* WorkflowExecutions is a struct that represents a list of workflow executions * WorkflowExecutions is a struct that represents a list of workflow executions
* Warning: No user can write (del, post, put) a workflow execution, it is only used by the system * Warning: No user can write (del, post, put) a workflow execution, it is only used by the system
* workflows generate their own executions * workflows generate their own executions
*/ */
type WorkflowExecutions struct { type WorkflowExecutions struct {
WorkflowID string `json:"workflow_id" bson:"workflow_id"` utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
ResourceID string `json:"resource_id" bson:"resource_id"` ExecDate time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required
Executions []*WorkflowExecution `json:"executions" bson:"executions"` EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow
State common.ScheduledType `json:"state" bson:"state" default:"0"` // State is the state of the workflow
WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
} }
// New - Creates a new instance of the WorkflowExecutions from a map func (r *WorkflowExecutions) StoreDraftDefault() {
func (dma *WorkflowExecutions) Deserialize(j map[string]interface{}) *WorkflowExecutions { r.IsDraft = true
b, err := json.Marshal(j) }
if err != nil {
return nil func (r *WorkflowExecutions) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
if r.State != set.(*WorkflowExecutions).State {
return true, &WorkflowExecutions{State: set.(*WorkflowExecutions).State} // only state can be updated
} }
json.Unmarshal(b, dma) return r.IsDraft, set // only draft buying can be updated
return dma
} }
// Serialize - Returns the WorkflowExecutions as a map func (r *WorkflowExecutions) CanDelete() bool {
func (dma *WorkflowExecutions) Serialize() map[string]interface{} { return r.IsDraft // only draft bookings can be deleted
var m map[string]interface{} }
b, err := json.Marshal(dma)
if err != nil { func (wfa *WorkflowExecutions) Equals(we *WorkflowExecutions) bool {
return nil return wfa.ExecDate.Equal(we.ExecDate) && wfa.WorkflowID == we.WorkflowID
}
func (ws *WorkflowExecutions) PurgeDraft(request *tools.APIRequest) error {
if ws.EndDate == nil {
// if no end... then Book like a savage
e := ws.ExecDate.Add(time.Hour)
ws.EndDate = &e
} }
json.Unmarshal(b, &m) accessor := ws.GetAccessor(request)
return m 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
"state": {{Operator: dbs.EQUAL.String(), Value: common.DRAFT.EnumIndex()}},
/* "workflow_id": {{Operator: dbs.EQUAL.String(), Value: ws.WorkflowID}},
* WorkflowExecution is a struct that represents a workflow execution "execution_date": {
* Warning: No user can write (del, post, put) a workflow execution, it is only used by the system {Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*ws.EndDate)},
* workflows generate their own executions {Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(ws.ExecDate)},
*/ },
type WorkflowExecution struct { },
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) }, "", ws.IsDraft)
ExecDate *time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required if code != 200 || err != nil {
EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow return err
State ScheduledType `json:"state" bson:"state" default:"0"` // State is the state of the workflow }
WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow for _, r := range res {
} accessor.DeleteOne(r.GetID())
}
func (wfa *WorkflowExecution) Equals(we *WorkflowExecution) bool { return nil
return wfa.ExecDate.Equal(*we.ExecDate) && wfa.WorkflowID == we.WorkflowID
} }
// tool to transform the argo status to a state // tool to transform the argo status to a state
func (wfa *WorkflowExecution) ArgoStatusToState(status string) *WorkflowExecution { func (wfa *WorkflowExecutions) ArgoStatusToState(status string) *WorkflowExecutions {
status = strings.ToLower(status) status = strings.ToLower(status)
switch status { switch status {
case "succeeded": // Succeeded case "succeeded": // Succeeded
wfa.State = SUCCESS wfa.State = common.SUCCESS
case "pending": // Pending case "pending": // Pending
wfa.State = SCHEDULED wfa.State = common.SCHEDULED
case "running": // Running case "running": // Running
wfa.State = STARTED wfa.State = common.STARTED
default: // Failed default: // Failed
wfa.State = FAILURE wfa.State = common.FAILURE
} }
return wfa return wfa
} }
func (r *WorkflowExecution) GenerateID() { func (r *WorkflowExecutions) GenerateID() {
r.UUID = uuid.New().String() r.UUID = uuid.New().String()
} }
func (d *WorkflowExecution) GetName() string { func (d *WorkflowExecutions) GetName() string {
return d.UUID + "_" + d.ExecDate.String() return d.UUID + "_" + d.ExecDate.String()
} }
func (d *WorkflowExecution) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *WorkflowExecutions) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.WORKFLOW_EXECUTION, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *WorkflowExecution) VerifyAuth(username string, peerID string, groups []string) bool { func (d *WorkflowExecutions) VerifyAuth(request *tools.APIRequest) bool {
return true return true
} }
func (d *WorkflowExecutions) Book(wf *workflow.Workflow) []*booking.Booking {
booking := []*booking.Booking{}
for _, p := range wf.ProcessingResources {
booking = append(booking, d.toItemBooking(wf.GetByRelatedProcessing(p.GetID(), wf.IsStorage))...)
booking = append(booking, d.toItemBooking(wf.GetByRelatedProcessing(p.GetID(), wf.IsProcessing))...)
}
return booking
}
func (d *WorkflowExecutions) toItemBooking(ss []resources.ShallowResourceInterface) []*booking.Booking {
items := []*booking.Booking{}
for _, s := range ss {
start := d.ExecDate
if s := s.GetLocationStart(); s != nil {
start = *s
}
end := start.Add(time.Duration(s.GetExplicitDurationInS()) * time.Second)
bookingItem := &booking.Booking{
State: common.DRAFT,
ResourceID: s.GetID(),
ResourceType: s.GetType(),
DestPeerID: s.GetCreatorID(),
ExpectedStartDate: start,
ExpectedEndDate: &end,
}
items = append(items, bookingItem)
}
return items
}

View File

@ -1,10 +1,12 @@
package workflow_execution package workflow_execution
import ( import (
"errors"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/common"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -13,57 +15,62 @@ type workflowExecutionMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor
} }
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workflowExecutionMongoAccessor { func NewAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor {
return &workflowExecutionMongoAccessor{ return &workflowExecutionMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: tools.WORKFLOW_EXECUTION,
User: username, // Set the caller
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
func (wfa *workflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (wfa *workflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, wfa) return nil, 404, errors.New("not implemented")
} }
func (wfa *workflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (wfa *workflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, wfa, &WorkflowExecution{}) return nil, 404, errors.New("not implemented")
} }
func (wfa *workflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (wfa *workflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, wfa) return nil, 404, errors.New("not implemented")
} }
func (wfa *workflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (wfa *workflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, wfa) return nil, 404, errors.New("not implemented")
} }
func (a *workflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *workflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*WorkflowExecutions](id, func(d utils.DBObject) (utils.DBObject, int, error) {
if d.(*WorkflowExecution).State == SCHEDULED && time.Now().UTC().After(*d.(*WorkflowExecution).ExecDate) { if d.(*WorkflowExecutions).State == common.DRAFT && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecution).State = FORGOTTEN utils.GenericDeleteOne(d.GetID(), a)
return nil, 404, errors.New("Not found")
}
if d.(*WorkflowExecutions).State == common.SCHEDULED && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecutions).State = common.FORGOTTEN
utils.GenericRawUpdateOne(d, id, a) utils.GenericRawUpdateOne(d, id, a)
} }
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *workflowExecutionMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *workflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), a) return utils.GenericLoadAll[*WorkflowExecutions](a.getExec(), isDraft, a)
} }
func (a *workflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *workflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*WorkflowExecution](filters, search, (&WorkflowExecution{}).GetObjectFilters(search), a.getExec(), a) return utils.GenericSearch[*WorkflowExecutions](filters, search, (&WorkflowExecutions{}).GetObjectFilters(search), a.getExec(), isDraft, a)
} }
func (a *workflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { func (a *workflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
if d.(*WorkflowExecution).State == SCHEDULED && time.Now().UTC().After(*d.(*WorkflowExecution).ExecDate) { if d.(*WorkflowExecutions).State == common.DRAFT && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecution).State = FORGOTTEN utils.GenericDeleteOne(d.GetID(), a)
return nil
}
if d.(*WorkflowExecutions).State == common.SCHEDULED && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecutions).State = common.FORGOTTEN
utils.GenericRawUpdateOne(d, d.GetID(), a) utils.GenericRawUpdateOne(d, d.GetID(), a)
} }
return d return d

View File

@ -0,0 +1,249 @@
package workflow_execution
import (
"errors"
"fmt"
"strings"
"time"
"cloud.o-forge.io/core/oc-lib/models/common"
"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"
"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/robfig/cron"
)
/*
* WorkflowSchedule is a struct that contains the scheduling information of a workflow
* It contains the mode of the schedule (Task or Service), the name of the schedule, the start and end time of the schedule and the cron expression
*/
// it's a flying object only use in a session time. It's not stored in the database
type WorkflowSchedule struct {
Workflow *workflow.Workflow `json:"workflow,omitempty"` // Workflow is the workflow dependancy of the schedule
WorkflowExecutions []*WorkflowExecutions `json:"workflow_executions,omitempty"` // WorkflowExecutions is the list of executions of the workflow
Message string `json:"message,omitempty"` // Message is the message of the schedule
Warning string `json:"warning,omitempty"` // Warning is the warning message of the schedule
Start time.Time `json:"start" validate:"required,ltfield=End"` // Start is the start time of the schedule, is required and must be less than the End time
End *time.Time `json:"end,omitempty"` // End is the end time of the schedule, is required and must be greater than the Start time
DurationS float64 `json:"duration_s" default:"-1"` // End is the end time of the schedule
Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task
}
func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule {
s, err := time.Parse("2006-01-02T15:04:05", start)
if err != nil {
return nil
}
ws := &WorkflowSchedule{
Start: s,
DurationS: durationInS,
Cron: cron,
}
e, err := time.Parse("2006-01-02T15:04:05", end)
if err == nil {
ws.End = &e
}
return ws
}
func (ws *WorkflowSchedule) CheckBooking(wfID string, caller *tools.HTTPCaller) (bool, *workflow.Workflow, []*WorkflowExecutions, error) {
if caller == nil && caller.URLS == nil && caller.URLS[tools.BOOKING] == nil || caller.URLS[tools.BOOKING][tools.POST] == "" {
return false, nil, []*WorkflowExecutions{}, errors.New("no caller defined")
}
access := workflow.NewAccessor(nil)
res, code, err := access.LoadOne(wfID)
if code != 200 {
return false, nil, []*WorkflowExecutions{}, errors.New("could not load the workflow with id: " + err.Error())
}
wf := res.(*workflow.Workflow)
wf, err = ws.planifyWorkflow(wf)
if err != nil {
return false, wf, []*WorkflowExecutions{}, err
}
ws.DurationS = wf.GetLongestTime(ws.End)
ws.Message = "We estimate that the workflow will start at " + ws.Start.String() + " and last " + fmt.Sprintf("%v", ws.DurationS) + "seconds."
if ws.End != nil && ws.Start.Add(time.Duration(wf.GetLongestTime(ws.End))*time.Second).After(*ws.End) {
ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n"
}
execs, err := ws.getExecutions(wf)
if err != nil {
return false, wf, []*WorkflowExecutions{}, err
}
for _, exec := range execs {
bookings := exec.Book(wf)
for _, booking := range bookings {
_, err := (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "",
tools.BOOKING, tools.POSTCHECK, booking.Serialize(booking), caller)
if err != nil {
return false, wf, execs, err
}
}
}
return true, wf, execs, nil
}
func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*workflow.Workflow, []*WorkflowExecutions, error) {
if request == nil {
return nil, []*WorkflowExecutions{}, errors.New("no request found")
}
c := request.Caller
if c == nil || c.URLS == nil || c.URLS[tools.BOOKING] == nil {
return nil, []*WorkflowExecutions{}, errors.New("no caller defined")
}
methods := c.URLS[tools.BOOKING]
if _, ok := methods[tools.POST]; !ok {
return nil, []*WorkflowExecutions{}, errors.New("no path found")
}
ok, wf, executions, err := ws.CheckBooking(wfID, request.Caller)
if !ok || err != nil {
return nil, []*WorkflowExecutions{}, errors.New("could not book the workflow" + fmt.Sprintf("%v", err))
}
ws.Workflow = wf
ws.WorkflowExecutions = executions
for _, exec := range executions {
err := exec.PurgeDraft(request)
if err != nil {
return nil, []*WorkflowExecutions{}, errors.New("could not book the workflow" + fmt.Sprintf("%v", err))
}
exec.GenerateID()
// Should DELETE the previous execution2
utils.GenericStoreOne(exec, NewAccessor(request))
}
return wf, executions, nil
}
func (ws *WorkflowSchedule) planifyWorkflow(wf *workflow.Workflow) (*workflow.Workflow, error) {
processings := []*resources.CustomizedProcessingResource{}
for _, item := range wf.GetGraphItems(wf.IsProcessing) {
realItem := item.GetResource().(*resources.CustomizedProcessingResource)
timeFromStartS := wf.Graph.GetAverageTimeProcessingBeforeStart(0, realItem.GetID())
started := ws.Start.Add(time.Duration(timeFromStartS) * time.Second)
wf.Graph.SetItemStartUsage(item.ID, started)
wf.Graph.SetItemEndUsage(item.ID, started.Add(time.Duration(realItem.ExplicitBookingDurationS)))
processings = append(processings, realItem)
}
for _, item := range wf.GetGraphItems(wf.IsData) {
wf.Graph.SetItemStartUsage(item.ID, ws.Start)
wf.Graph.SetItemEndUsage(item.ID, *ws.End)
}
for _, f := range []func(graph.GraphItem) bool{wf.IsStorage, wf.IsCompute} {
for _, item := range wf.GetGraphItems(f) {
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(ws.Start, processings, item.GetResource(),
func(i graph.GraphItem) resources.ShallowResourceInterface {
if f(i) {
return i.GetResource()
} else {
return nil
}
})
started := ws.Start.Add(time.Duration(nearestStart) * time.Second)
wf.Graph.SetItemStartUsage(item.ID, started)
if longestDuration >= 0 {
wf.Graph.SetItemEndUsage(item.ID, started.Add(time.Duration(longestDuration)))
}
}
}
for _, item := range wf.GetGraphItems(wf.IsWorkflow) {
access := workflow.NewAccessor(nil)
res, code, err := access.LoadOne(item.GetResource().GetID())
if code != 200 || err != nil {
return nil, errors.New("could not load the workflow with id: " + fmt.Sprintf("%v", err.Error()))
}
innerWF := res.(*workflow.Workflow)
innerWF, err = ws.planifyWorkflow(innerWF)
started := ws.Start.Add(time.Duration(innerWF.GetNearestStart(ws.Start)) * time.Second)
wf.Graph.SetItemStartUsage(item.ID, started)
durationE := time.Duration(innerWF.GetLongestTime(ws.End))
if durationE < 0 {
continue
}
ended := ws.Start.Add(durationE * time.Second)
wf.Graph.SetItemEndUsage(item.ID, ended)
}
return wf, nil
}
/*
BOOKING IMPLIED TIME, not of subscription but of execution
so is processing time execution time applied on computes
data can improve the processing time
time should implied a security time border (10sec) if not from the same executions
VERIFY THAT WE HANDLE DIFFERENCE BETWEEN LOCATION TIME && BOOKING
*/
/*
* getExecutions is a function that returns the executions of a workflow
* it returns an array of workflow_execution.WorkflowExecution
*/
func (ws *WorkflowSchedule) getExecutions(workflow *workflow.Workflow) ([]*WorkflowExecutions, error) {
workflows_executions := []*WorkflowExecutions{}
dates, err := ws.getDates()
if err != nil {
return workflows_executions, err
}
for _, date := range dates {
obj := &WorkflowExecutions{
AbstractObject: utils.AbstractObject{
Name: workflow.Name + "_execution_" + date.Start.String(), // set the name of the execution
},
ExecDate: date.Start, // set the execution date
EndDate: date.End, // set the end date
State: common.DRAFT, // set the state to 1 (scheduled)
WorkflowID: workflow.GetID(), // set the workflow id dependancy of the execution
}
workflows_executions = append(workflows_executions, obj)
}
return workflows_executions, nil
}
func (ws *WorkflowSchedule) getDates() ([]Schedule, error) {
schedule := []Schedule{}
if len(ws.Cron) > 0 { // if cron is set then end date should be set
if ws.End == nil {
return schedule, errors.New("a cron task should have an end date.")
}
if ws.DurationS <= 0 {
ws.DurationS = ws.End.Sub(ws.Start).Seconds()
}
cronStr := strings.Split(ws.Cron, " ") // split the cron string to treat it
if len(cronStr) < 6 { // if the cron string is less than 6 fields, return an error because format is : ss mm hh dd MM dw (6 fields)
return schedule, errors.New("Bad cron message: (" + ws.Cron + "). Should be at least ss mm hh dd MM dw")
}
subCron := strings.Join(cronStr[:6], " ")
// cron should be parsed as ss mm hh dd MM dw t (min 6 fields)
specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) // create a new cron parser
sched, err := specParser.Parse(subCron) // parse the cron string
if err != nil {
return schedule, errors.New("Bad cron message: " + err.Error())
}
// loop through the cron schedule to set the executions
for s := sched.Next(ws.Start); !s.IsZero() && s.Before(*ws.End); s = sched.Next(s) {
e := s.Add(time.Duration(ws.DurationS) * time.Second)
schedule = append(schedule, Schedule{
Start: s,
End: &e,
})
}
} else { // if no cron, set the execution to the start date
schedule = append(schedule, Schedule{
Start: ws.Start,
End: ws.End,
})
}
return schedule, nil
}
type Schedule struct {
Start time.Time
End *time.Time
}
/*
* TODO : LARGEST GRAIN PLANIFYING THE WORKFLOW WHEN OPTION IS SET
* SET PROTECTION BORDER TIME
*/

View File

@ -1,6 +1,8 @@
package workspace package workspace
import ( import (
"fmt"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
"cloud.o-forge.io/core/oc-lib/models/resources" "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/utils"
@ -16,17 +18,20 @@ type Workspace struct {
Shared string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workspace Shared string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workspace
} }
func (d *Workspace) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *Workspace) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.WORKSPACE, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (ao *Workspace) VerifyAuth(username string, peerID string, groups []string) bool { func (ao *Workspace) VerifyAuth(request *tools.APIRequest) bool {
fmt.Println("Workspace.VerifyAuth", ao.Shared)
if ao.Shared != "" { if ao.Shared != "" {
shared, code, _ := shallow_collaborative_area.New(tools.COLLABORATIVE_AREA, username, peerID, groups, nil).LoadOne(ao.Shared) shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared)
fmt.Println("Workspace.VerifyAuth", shared, code)
if code != 200 || shared == nil { if code != 200 || shared == nil {
return false return false
} }
return shared.VerifyAuth(username, peerID, groups) return shared.VerifyAuth(request)
} }
return ao.AbstractObject.VerifyAuth(username, peerID, groups) fmt.Println("Workspace.VerifyAuth", ao.AbstractObject.VerifyAuth(request))
return ao.AbstractObject.VerifyAuth(request)
} }

View File

@ -8,8 +8,8 @@ import (
type WorkspaceHistory struct{ Workspace } type WorkspaceHistory struct{ Workspace }
func (d *WorkspaceHistory) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { func (d *WorkspaceHistory) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New(tools.WORKFLOW_HISTORY, username, peerID, groups, caller) // Create a new instance of the accessor return NewAccessorHistory(request) // Create a new instance of the accessor
} }
func (r *WorkspaceHistory) GenerateID() { func (r *WorkspaceHistory) GenerateID() {
r.UUID = uuid.New().String() r.UUID = uuid.New().String()

View File

@ -18,15 +18,21 @@ type workspaceMongoAccessor struct {
} }
// New creates a new instance of the workspaceMongoAccessor // New creates a new instance of the workspaceMongoAccessor
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workspaceMongoAccessor { func NewAccessorHistory(request *tools.APIRequest) *workspaceMongoAccessor {
return new(tools.WORKSPACE_HISTORY, request)
}
func NewAccessor(request *tools.APIRequest) *workspaceMongoAccessor {
return new(tools.WORKSPACE, request)
}
// New creates a new instance of the workspaceMongoAccessor
func new(t tools.DataType, request *tools.APIRequest) *workspaceMongoAccessor {
return &workspaceMongoAccessor{ return &workspaceMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Caller: caller, Request: request,
PeerID: peerID, Type: t,
User: username,
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
@ -36,7 +42,7 @@ func New(t tools.DataType, username string, peerID string, groups []string, call
func (a *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (a *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
res, code, err := utils.GenericDeleteOne(id, a) res, code, err := utils.GenericDeleteOne(id, a)
if code == 200 && res != nil { if code == 200 && res != nil {
a.share(res.(*Workspace), tools.DELETE, a.Caller) // Share the deletion to the peers a.share(res.(*Workspace), tools.DELETE, a.GetCaller()) // Share the deletion to the peers
} }
return res, code, err return res, code, err
} }
@ -46,7 +52,7 @@ func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils
d := set.(*Workspace) // Get the workspace from the set d := set.(*Workspace) // Get the workspace from the set
d.Clear() d.Clear()
if d.Active { // If the workspace is active, deactivate all the other workspaces if d.Active { // If the workspace is active, deactivate all the other workspaces
res, _, err := a.LoadAll() res, _, err := a.LoadAll(true)
if err == nil { if err == nil {
for _, r := range res { for _, r := range res {
if r.GetID() != id { if r.GetID() != id {
@ -58,7 +64,7 @@ func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils
} }
res, code, err := utils.GenericUpdateOne(set, id, a, &Workspace{}) res, code, err := utils.GenericUpdateOne(set, id, a, &Workspace{})
if code == 200 && res != nil { if code == 200 && res != nil {
a.share(res.(*Workspace), tools.PUT, a.Caller) a.share(res.(*Workspace), tools.PUT, a.GetCaller())
} }
return res, code, err return res, code, err
} }
@ -70,8 +76,8 @@ func (a *workspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject,
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: data.GetName() + "_workspace"}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: data.GetName() + "_workspace"}},
}, },
} }
res, _, err := a.Search(filters, "") // Search for the workspace res, _, err := a.Search(filters, "", true) // Search for the workspace
if err == nil && len(res) > 0 { // If the workspace already exists, return an error if err == nil && len(res) > 0 { // If the workspace already exists, return an error
return nil, 409, errors.New("A workspace with the same name already exists") return nil, 409, errors.New("A workspace with the same name already exists")
} }
// reset the resources // reset the resources
@ -87,23 +93,23 @@ func (a *workspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, i
func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Workspace](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Workspace](id, func(d utils.DBObject) (utils.DBObject, int, error) {
d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) d.(*Workspace).Fill(a.GetRequest())
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *workspaceMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { func (a *workspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Workspace](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[*Workspace](func(d utils.DBObject) utils.ShallowDBObject {
d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) d.(*Workspace).Fill(a.GetRequest())
return d return d
}, a) }, isDraft, a)
} }
func (a *workspaceMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { func (a *workspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Workspace](filters, search, (&Workspace{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericSearch[*Workspace](filters, search, (&Workspace{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) d.(*Workspace).Fill(a.GetRequest())
return d return d
}, a) }, isDraft, a)
} }
/* /*
@ -115,7 +121,7 @@ func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD,
return return
} }
shallow := &shallow_collaborative_area.ShallowCollaborativeArea{} shallow := &shallow_collaborative_area.ShallowCollaborativeArea{}
access := (shallow).GetAccessor(a.GetUser(), a.PeerID, a.Groups, nil) access := (shallow).GetAccessor(a.GetRequest())
res, code, _ := access.LoadOne(realData.Shared) res, code, _ := access.LoadOne(realData.Shared)
if code != 200 { if code != 200 {
return return

View File

@ -11,6 +11,13 @@ import (
beego "github.com/beego/beego/v2/server/web" beego "github.com/beego/beego/v2/server/web"
) )
type APIRequest struct {
Username string
PeerID string
Groups []string
Caller *HTTPCaller
}
/* /*
* API is the Health Check API * API is the Health Check API
* it defines the health check methods * it defines the health check methods

View File

@ -20,6 +20,8 @@ const (
BOOKING BOOKING
WORKFLOW_HISTORY WORKFLOW_HISTORY
WORKSPACE_HISTORY WORKSPACE_HISTORY
ORDER
BUYING_STATUS
) )
var NOAPI = "" var NOAPI = ""
@ -48,6 +50,8 @@ var DefaultAPI = [...]string{
DATACENTERAPI, DATACENTERAPI,
NOAPI, NOAPI,
NOAPI, NOAPI,
NOAPI,
NOAPI,
} }
// Bind the standard data name to the data type // Bind the standard data name to the data type
@ -68,6 +72,8 @@ var Str = [...]string{
"booking", "booking",
"workflow_history", "workflow_history",
"workspace_history", "workspace_history",
"order",
"buying_status",
} }
func FromInt(i int) string { func FromInt(i int) string {

View File

@ -16,6 +16,7 @@ const (
GET METHOD = iota GET METHOD = iota
PUT PUT
POST POST
POSTCHECK
DELETE DELETE
STRICT_INTERNAL_GET STRICT_INTERNAL_GET
@ -26,7 +27,7 @@ const (
// String returns the string of the enum // String returns the string of the enum
func (m METHOD) String() string { func (m METHOD) String() string {
return [...]string{"GET", "PUT", "POST", "DELETE", "INTERNALGET", "INTERNALPUT", "INTERNALPOST", "INTERNALDELETE"}[m] return [...]string{"GET", "PUT", "POST", "POST", "DELETE", "INTERNALGET", "INTERNALPUT", "INTERNALPOST", "INTERNALDELETE"}[m]
} }
// EnumIndex returns the index of the enum // EnumIndex returns the index of the enum
@ -36,7 +37,7 @@ func (m METHOD) EnumIndex() int {
// ToMethod returns the method from a string // ToMethod returns the method from a string
func ToMethod(str string) METHOD { func ToMethod(str string) METHOD {
for _, s := range []METHOD{GET, PUT, POST, DELETE, for _, s := range []METHOD{GET, PUT, POST, POSTCHECK, DELETE,
STRICT_INTERNAL_GET, STRICT_INTERNAL_PUT, STRICT_INTERNAL_POST, STRICT_INTERNAL_DELETE} { STRICT_INTERNAL_GET, STRICT_INTERNAL_PUT, STRICT_INTERNAL_POST, STRICT_INTERNAL_DELETE} {
if s.String() == str { if s.String() == str {
return s return s