Compare commits

...

107 Commits

Author SHA1 Message Date
mr
db85d1a48b adding inputs output struct based on argo naming for now 2025-01-27 16:03:45 +01:00
mr
3ff7b47995 adding inputs output struct based on argo naming for now 2025-01-27 16:02:45 +01:00
mr
8b03df7923 adding inputs output struct based on argo naming for now 2025-01-27 14:42:57 +01:00
mr
98dc733240 adding inputs output struct based on argo naming for now 2025-01-27 14:41:03 +01:00
mr
c02e3dffcf adding inputs output struct based on argo naming for now 2025-01-27 14:38:44 +01:00
mr
1cdbcca7f7 adding inputs output struct based on argo naming for now 2025-01-27 14:31:12 +01:00
mr
9b8acb83cb adding inputs output struct based on argo naming for now 2025-01-27 14:30:47 +01:00
mr
7ca360be6a adding inputs output struct based on argo naming for now 2025-01-27 14:15:12 +01:00
mr
6c4fab1adc adding inputs output struct based on argo naming for now 2025-01-27 14:01:42 +01:00
mr
6551b1b97d adding inputs output struct based on argo naming for now 2025-01-27 14:00:20 +01:00
mr
71d9bd4678 adding inputs output struct based on argo naming for now 2025-01-27 13:24:14 +01:00
mr
1ad9ce09cb adding inputs output struct based on argo naming for now 2025-01-27 12:09:38 +01:00
mr
d731277914 adding inputs output struct based on argo naming for now 2025-01-27 12:04:48 +01:00
mr
24fe99cfa5 adding inputs output struct based on argo naming for now 2025-01-27 11:40:10 +01:00
mr
2a373e7368 adding inputs output struct based on argo naming for now 2025-01-27 11:36:57 +01:00
mr
68bacf5da4 adding inputs output struct based on argo naming for now 2025-01-27 09:37:56 +01:00
mr
ed158ffdcb adding inputs output struct based on argo naming for now 2025-01-27 09:19:37 +01:00
mr
fbb55e64dc adding inputs output struct based on argo naming for now 2025-01-27 09:05:47 +01:00
mr
1521b8fac5 adding inputs output struct based on argo naming for now 2025-01-24 15:33:57 +01:00
mr
97d466818a light modification 2025-01-24 10:55:57 +01:00
mr
c1888f8921 light modification 2025-01-23 15:08:34 +01:00
mr
db6049bab3 light modification 2025-01-23 14:47:17 +01:00
mr
5cc68bca6d light modification 2025-01-23 14:36:19 +01:00
mr
49e495f062 light modification 2025-01-23 12:49:59 +01:00
mr
1952d905d2 light modification 2025-01-23 12:47:04 +01:00
mr
2205ac9b58 light modification 2025-01-23 11:35:35 +01:00
mr
e9017767d1 light modification 2025-01-23 11:31:56 +01:00
mr
ad660b0ce8 light modification 2025-01-23 11:28:48 +01:00
mr
d15fdac27b light modification 2025-01-23 10:49:50 +01:00
mr
386881c283 light modification 2025-01-23 10:33:43 +01:00
mr
8cba10c4fe light modification 2025-01-23 09:27:27 +01:00
mr
f8ac3154e1 light modification 2025-01-23 09:06:22 +01:00
mr
df04133551 light modification 2025-01-23 08:48:22 +01:00
mr
99693d8ec0 light modification 2025-01-23 08:35:28 +01:00
mr
0e798dac50 light modification 2025-01-22 16:30:05 +01:00
mr
e6ac7d0da6 light modification 2025-01-22 16:17:55 +01:00
mr
9c71730d9c light modification 2025-01-22 15:03:40 +01:00
mr
4be954a6f3 light modification 2025-01-22 14:53:42 +01:00
mr
e9278111a6 light modification 2025-01-22 14:13:10 +01:00
mr
ed1e761052 light modification 2025-01-22 13:18:14 +01:00
mr
86b1e4ad5d light modification 2025-01-22 13:12:14 +01:00
mr
062c1afe85 light modification 2025-01-22 12:04:38 +01:00
mr
fa00980352 light modification 2025-01-22 11:55:02 +01:00
mr
2a93b17d71 light modification 2025-01-22 11:16:35 +01:00
mr
287aa3dea3 light modification 2025-01-22 11:11:04 +01:00
mr
8ab313e6cb light modification 2025-01-22 10:07:36 +01:00
mr
cccb54d38f light modification 2025-01-22 09:59:28 +01:00
mr
67940296d2 light modification 2025-01-22 09:06:53 +01:00
mr
67ebeca1f4 light modification 2025-01-21 17:04:38 +01:00
mr
b45e882559 light modification 2025-01-21 16:50:49 +01:00
mr
745bb58c59 light modification 2025-01-21 14:10:07 +01:00
mr
bf5a16f41b light modification 2025-01-21 11:55:44 +01:00
mr
bc12fb53be light modification 2025-01-21 11:11:18 +01:00
mr
0d83885b9b light modification 2025-01-21 09:35:41 +01:00
mr
de585a7234 light modification 2025-01-21 09:02:57 +01:00
mr
5c2980fb36 light modification 2025-01-21 09:02:26 +01:00
mr
e741a95cdb light modification 2025-01-21 08:58:04 +01:00
mr
19eb5239a6 light modification 2025-01-21 08:36:10 +01:00
mr
305f260503 light modification 2025-01-20 15:35:09 +01:00
mr
d1f6331ff8 light modification 2025-01-20 14:43:02 +01:00
mr
67b8215adf light modification 2025-01-20 13:49:39 +01:00
mr
58b36f2823 light modification 2025-01-20 13:37:06 +01:00
mr
2452d37acf light modification 2025-01-20 13:29:04 +01:00
mr
8e4ebbf622 light modification 2025-01-20 13:26:30 +01:00
mr
b85ca8674b light modification 2025-01-17 16:22:46 +01:00
mr
c63a1fef6c light modification 2025-01-17 14:54:17 +01:00
mr
66196da877 light modification 2025-01-17 14:45:35 +01:00
mr
e5c7dbe4cb light modification 2025-01-17 13:48:01 +01:00
mr
f72ceecc19 light modification 2025-01-17 13:46:36 +01:00
mr
ed787683f4 light modification 2025-01-17 13:19:20 +01:00
mr
d44fb976e4 inspect search bug 2025-01-17 11:05:08 +01:00
mr
fb1b44e1d1 light modification 2025-01-17 10:56:45 +01:00
mr
d00109daf3 light modification 2025-01-17 10:55:06 +01:00
mr
367613a9d5 light modification 2025-01-17 10:34:44 +01:00
mr
b990fe42d3 inspect search bug 2025-01-17 10:07:37 +01:00
mr
7d11c23eba inspect search bug 2025-01-17 09:51:50 +01:00
mr
450fab437c inspect search bug 2025-01-17 09:16:40 +01:00
mr
a4a249bab8 light modification 2025-01-16 15:25:44 +01:00
mr
d9c9f05cd2 light modification 2025-01-16 15:14:56 +01:00
mr
68f4189283 light modification 2025-01-16 10:14:55 +01:00
mr
0e0540af43 light modification 2025-01-15 11:28:20 +01:00
mr
555c5acb26 light modification 2025-01-15 11:09:33 +01:00
mr
b48e2cb3e5 light modification 2025-01-15 11:02:00 +01:00
mr
cf1c5f2186 light modification 2025-01-15 11:01:13 +01:00
mr
be38030395 light modification 2025-01-15 10:56:44 +01:00
mr
ad69c04951 light modification 2025-01-15 09:20:26 +01:00
mr
abd6c1d712 light modification 2025-01-14 15:37:30 +01:00
mr
a55f4c449c light modification 2025-01-14 15:03:29 +01:00
mr
1a4694c891 light modification 2025-01-14 14:50:55 +01:00
mr
b782248da7 light modification 2025-01-14 11:53:39 +01:00
mr
ae9a80c8f3 light modification 2025-01-14 11:28:16 +01:00
mr
918006302b data change for resource struct 2025-01-14 09:16:37 +01:00
mr
f30076e0f5 data change for resource struct 2025-01-14 09:15:50 +01:00
mr
78157b80d2 kick out geopoint useless 2025-01-14 08:42:12 +01:00
mr
4309309bc9 add exec 2025-01-14 08:32:41 +01:00
mr
0feab329c1 add exec 2025-01-14 08:32:15 +01:00
mr
1c32cd2d12 add exec 2025-01-14 08:17:22 +01:00
mr
a0f436b3e1 add exec 2025-01-13 14:52:41 +01:00
mr
6e5c873796 add exec 2025-01-13 13:48:12 +01:00
mr
3cdb43074b add exec 2025-01-13 13:09:43 +01:00
mr
11905339bb add exec 2025-01-13 12:42:56 +01:00
mr
301ef8dc05 add exec 2025-01-13 12:25:57 +01:00
mr
0e84db61de add exec 2025-01-13 12:06:28 +01:00
mr
21a7ff9010 Order Flow Payment Draft 2025-01-13 11:24:07 +01:00
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
65 changed files with 3617 additions and 1514 deletions

View File

@ -3,7 +3,6 @@ package mongo
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"slices" "slices"
"time" "time"
@ -171,8 +170,8 @@ func (m *MongoDB) DeleteOne(id string, collection_name string) (int64, int, erro
filter := bson.M{"_id": id} filter := bson.M{"_id": id}
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}}) opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
result, err := targetDBCollection.DeleteOne(MngoCtx, filter, opts) result, err := targetDBCollection.DeleteOne(MngoCtx, filter, opts)
if err != nil { if err != nil {
@ -192,8 +191,8 @@ func (m *MongoDB) DeleteMultiple(f map[string]interface{}, collection_name strin
} }
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}}) opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
result, err := targetDBCollection.DeleteMany(MngoCtx, filter, opts) result, err := targetDBCollection.DeleteMany(MngoCtx, filter, opts)
if err != nil { if err != nil {
@ -215,8 +214,8 @@ func (m *MongoDB) UpdateMultiple(set interface{}, filter map[string]interface{},
f = append(f, bson.E{Key: k, Value: v}) f = append(f, bson.E{Key: k, Value: v})
} }
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 50*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
res, err := targetDBCollection.UpdateMany(MngoCtx, f, dbs.InputToBson(doc, true)) res, err := targetDBCollection.UpdateMany(MngoCtx, f, dbs.InputToBson(doc, true))
if err != nil { if err != nil {
m.Logger.Error().Msg("Couldn't update resource: " + err.Error()) m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
@ -234,8 +233,8 @@ func (m *MongoDB) UpdateOne(set interface{}, id string, collection_name string)
bson.Unmarshal(b, &doc) bson.Unmarshal(b, &doc)
filter := bson.M{"_id": id} filter := bson.M{"_id": id}
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 50*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
_, err := targetDBCollection.UpdateOne(MngoCtx, filter, dbs.InputToBson(doc, true)) _, err := targetDBCollection.UpdateOne(MngoCtx, filter, dbs.InputToBson(doc, true))
if err != nil { if err != nil {
m.Logger.Error().Msg("Couldn't update resource: " + err.Error()) m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
@ -253,8 +252,8 @@ func (m *MongoDB) StoreOne(obj interface{}, id string, collection_name string) (
bson.Unmarshal(b, &doc) bson.Unmarshal(b, &doc)
doc["_id"] = id doc["_id"] = id
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
_, err := targetDBCollection.InsertOne(MngoCtx, doc) _, err := targetDBCollection.InsertOne(MngoCtx, doc)
if err != nil { if err != nil {
@ -271,8 +270,8 @@ func (m *MongoDB) LoadOne(id string, collection_name string) (*mongo.SingleResul
} }
filter := bson.M{"_id": id} filter := bson.M{"_id": id}
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
res := targetDBCollection.FindOne(MngoCtx, filter) res := targetDBCollection.FindOne(MngoCtx, filter)
if res.Err() != nil { if res.Err() != nil {
@ -289,7 +288,6 @@ func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.C
} }
opts := options.Find() opts := options.Find()
opts.SetLimit(100) opts.SetLimit(100)
fmt.Println("Filters: ", CollectionMap, collection_name)
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
orList := bson.A{} orList := bson.A{}
andList := bson.A{} andList := bson.A{}
@ -315,8 +313,8 @@ func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.C
} }
} }
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // defer cancel()
if cursor, err := targetDBCollection.Find( if cursor, err := targetDBCollection.Find(
MngoCtx, MngoCtx,
f, f,
@ -338,8 +336,8 @@ func (m *MongoDB) LoadFilter(filter map[string]interface{}, collection_name stri
} }
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
res, err := targetDBCollection.Find(MngoCtx, f) res, err := targetDBCollection.Find(MngoCtx, f)
if err != nil { if err != nil {
@ -355,8 +353,8 @@ func (m *MongoDB) LoadAll(collection_name string) (*mongo.Cursor, int, error) {
} }
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() //defer cancel()
res, err := targetDBCollection.Find(MngoCtx, bson.D{}) res, err := targetDBCollection.Find(MngoCtx, bson.D{})
if err != nil { if err != nil {

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 " ResourceInstanceITF
AbstractCustomizedResource "1 " --* "1 " ResourceInstanceITF
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 ResourceInstanceITF {
GetID() string
VerifyPartnership() bool // eval if there is one partnership per peer groups in every instance
GetPeerGroups() []ResourcePartnerITF, []map[string][]string
ClearPeerGroups()
}
ResourceInstanceITF -- 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,9 +17,9 @@ 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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
w2 "cloud.o-forge.io/core/oc-lib/models/workflow" w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/models/workflow_execution"
@ -50,6 +50,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
@ -195,49 +196,6 @@ func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string,
}() }()
logs.CreateLogger("main") logs.CreateLogger("main")
mongo.MONGOService.Init(models.GetModelsNames(), config.GetConfig()) // init the mongo service mongo.MONGOService.Init(models.GetModelsNames(), config.GetConfig()) // init the mongo service
/*
Here we will check if the resource model is already stored in the database
If not we will store it
Resource model is the model that will define the structure of the resources
*/
accessor := (&resource_model.ResourceModel{}).GetAccessor("", "", []string{}, 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()} {
data, code, _ := accessor.Search(nil, model)
if code == 404 || len(data) == 0 {
refs := map[string]string{}
m := map[string]resource_model.Model{}
// TODO Specify the model for each resource
// for now only processing is specified here (not an elegant way)
if model == tools.DATA_RESOURCE.String() || model == tools.STORAGE_RESOURCE.String() {
refs["path"] = "string"
}
if model == tools.PROCESSING_RESOURCE.String() {
m["command"] = resource_model.Model{
Type: "string",
ReadOnly: false,
}
m["args"] = resource_model.Model{
Type: "string",
ReadOnly: false,
}
m["env"] = resource_model.Model{
Type: "string",
ReadOnly: false,
}
m["volumes"] = resource_model.Model{
Type: "map[string]string",
ReadOnly: false,
}
}
accessor.StoreOne(&resource_model.ResourceModel{
ResourceType: model,
VarRefs: refs,
Model: map[string]map[string]resource_model.Model{
"container": m,
},
})
}
}
return cfg return cfg
} }
@ -279,6 +237,63 @@ 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, scheduler *workflow_execution.WorkflowSchedule) (*workflow_execution.WorkflowSchedule, error) {
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, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
})
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 +302,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 +329,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 +364,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 +394,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 +422,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 +451,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 +480,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 +497,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
} }

1
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

2
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=

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/enum"
"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,100 @@ 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
WorkflowID string `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
State enum.BookingStatus `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:"resource_id,omitempty" bson:"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: enum.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/enum"
"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 = enum.FORGOTTEN
utils.GenericRawUpdateOne(d, id, a)
} else if d.(*Booking).State == enum.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) {
d.(*Booking).State = enum.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 == enum.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) {
d.(*Booking).State = workflow_execution.FORGOTTEN d.(*Booking).State = enum.DELAYED
utils.GenericRawUpdateOne(d, d.GetID(), a) utils.GenericRawUpdateOne(d, d.GetID(), a)
} }
return d return d

View File

@ -1,7 +1,6 @@
package collaborative_area package collaborative_area
import ( import (
"fmt"
"slices" "slices"
"time" "time"
@ -44,6 +43,9 @@ type CollaborativeArea struct {
} }
func (ao *CollaborativeArea) Clear(peerID string) { func (ao *CollaborativeArea) Clear(peerID string) {
if ao.AllowedPeersGroup == nil {
ao.AllowedPeersGroup = map[string][]string{}
}
ao.CreatorID = peerID ao.CreatorID = peerID
if config.GetConfig().Whitelist { if config.GetConfig().Whitelist {
ao.AllowedPeersGroup[peerID] = []string{"*"} ao.AllowedPeersGroup[peerID] = []string{"*"}
@ -69,25 +71,24 @@ 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)
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

@ -1,7 +1,6 @@
package collaborative_area package collaborative_area
import ( import (
"errors"
"fmt" "fmt"
"slices" "slices"
@ -26,19 +25,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,11 +66,10 @@ 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") if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
if code != 200 || len(dd) == 0 { data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{}
return nil, code, errors.New("Could not retrieve the peer" + err.Error())
} }
data.(*CollaborativeArea).CollaborativeAreaRule.Creator = dd[0].GetID() data.(*CollaborativeArea).CollaborativeAreaRule.Creator = id
d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a) d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a)
if code == 200 { if code == 200 {
a.sharedWorkflow(d.(*CollaborativeArea), d.GetID()) // create all shared workflows a.sharedWorkflow(d.(*CollaborativeArea), d.GetID()) // create all shared workflows
@ -88,13 +84,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 +100,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 +145,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 +161,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 +171,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 +201,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 +223,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 +231,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 +256,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 +266,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,20 @@
package enum
type InfrastructureType int
const (
DOCKER InfrastructureType = iota
KUBERNETES
SLURM
HW
CONDOR
)
func (t InfrastructureType) String() string {
return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
}
// get list of all infrastructure types
func InfrastructureList() []InfrastructureType {
return []InfrastructureType{DOCKER, KUBERNETES, SLURM, HW, CONDOR}
}

View File

@ -0,0 +1,54 @@
package enum
type StorageSize int
// StorageType - Enum that defines the type of storage
const (
GB StorageSize = iota
MB
KB
TB
)
var argoType = [...]string{
"Gi",
"Mi",
"Ki",
"Ti",
}
// Size to string
func (t StorageSize) String() string {
return [...]string{"GB", "MB", "KB", "TB"}[t]
}
func SizeList() []StorageSize {
return []StorageSize{GB, MB, KB, TB}
}
// 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
)
// String() - Returns the string representation of the storage type
func (t StorageType) String() string {
return [...]string{"FILE", "STREAM", "API", "DATABASE", "S3", "MEMORY", "HARDWARE"}[t]
}
func TypeList() []StorageType {
return []StorageType{FILE, STREAM, API, DATABASE, S3, MEMORY, HARDWARE}
}

View File

@ -0,0 +1,64 @@
package enum
type CompletionStatus int
const (
DRAFTED CompletionStatus = iota
PENDING
CANCEL
PARTIAL
PAID
DISPUTED
OVERDUE
REFUND
)
func (d CompletionStatus) String() string {
return [...]string{"drafted", "pending", "cancel", "partial", "paid", "disputed", "overdue", "refund"}[d]
}
func CompletionStatusList() []CompletionStatus {
return []CompletionStatus{DRAFTED, PENDING, CANCEL, PARTIAL, PAID, DISPUTED, OVERDUE, REFUND}
}
type BookingStatus int
const (
DRAFT BookingStatus = 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 BookingStatus) String() string {
return str[d]
}
// EnumIndex - Creating common behavior-give the type a EnumIndex functio
func (d BookingStatus) EnumIndex() int {
return int(d)
}
// List
func StatusList() []BookingStatus {
return []BookingStatus{DRAFT, SCHEDULED, STARTED, FAILURE, SUCCESS, FORGOTTEN, DELAYED, CANCELLED}
}

View File

@ -0,0 +1,17 @@
package models
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
}

View File

@ -0,0 +1,20 @@
package models
// CPU is a struct that represents a CPU
type CPU struct {
Model string `bson:"model,omitempty" json:"model,omitempty"`
FrequencyGhz float64 `bson:"frequency,omitempty" json:"frequency,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:"model,omitempty" json:"model,omitempty"`
MemoryGb float64 `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"`
Cores map[string]int `bson:"cores,omitempty" json:"cores,omitempty"`
}

View File

@ -0,0 +1,19 @@
package models
type Artifact struct {
AttrPath string `json:"attr_path,omitempty" bson:"attr_path,omitempty" validate:"required"`
AttrFrom string `json:"from_path,omitempty" bson:"from_path,omitempty"`
Readonly bool `json:"readonly" bson:"readonly" default:"true"`
}
type Param struct {
Attr string `json:"attr" bson:"attr" validate:"required"`
Value string `json:"value,omitempty" bson:"value,omitempty"`
Readonly bool `json:"readonly" bson:"readonly" default:"true"`
Optionnal bool `json:"optionnal" bson:"optionnal" default:"true"`
}
type InOutputs struct {
Params []Param `json:"parameters" bson:"parameters"`
Artifacts []Artifact `json:"artifacts" bson:"artifacts"`
}

42
models/common/planner.go Normal file
View File

@ -0,0 +1,42 @@
package common
import (
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/tools"
)
func GetPlannerNearestStart(start time.Time, planned map[tools.DataType][]pricing.PricedItemITF, request *tools.APIRequest) float64 {
near := float64(10000000000) // set a high value
for _, items := range planned { // loop through the planned items
for _, priced := range items { // loop through the priced items
if priced.GetLocationStart() == nil { // if the start is nil,
continue // skip the iteration
}
newS := priced.GetLocationStart() // get the start
if newS.Sub(start).Seconds() < near { // if the difference between the start and the new start is less than the nearest start
near = newS.Sub(start).Seconds()
}
}
}
return near
}
func GetPlannerLongestTime(end *time.Time, planned map[tools.DataType][]pricing.PricedItemITF, request *tools.APIRequest) float64 {
if end == nil {
return -1
}
longestTime := float64(0)
for _, priced := range planned[tools.PROCESSING_RESOURCE] {
if priced.GetLocationEnd() == nil {
continue
}
newS := priced.GetLocationEnd()
if end == nil && longestTime < newS.Sub(*end).Seconds() {
longestTime = newS.Sub(*end).Seconds()
}
// get the nearest start from start var
}
return longestTime
}

View File

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

View File

@ -0,0 +1,64 @@
package pricing
import (
"time"
)
type PricingProfileITF interface {
GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
IsPurchased() bool
GetOverrideStrategyValue() int
}
type RefundType int
const (
REFUND_DEAD_END RefundType = iota
REFUND_ON_ERROR
REFUND_ON_EARLY_END
)
func (t RefundType) String() string {
return [...]string{"REFUND ON DEAD END", "REFUND ON ERROR", "REFUND ON EARLY END"}[t]
}
func RefundTypeList() []RefundType {
return []RefundType{REFUND_DEAD_END, REFUND_ON_ERROR, REFUND_ON_EARLY_END}
}
type AccessPricingProfile[T Strategy] struct { // only use for acces such as : DATA && PROCESSING
Pricing PricingStrategy[T] `json:"pricing,omitempty" bson:"pricing,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]) GetOverrideStrategyValue() int {
return -1
}
type ExploitPrivilegeStrategy int
const (
BASIC ExploitPrivilegeStrategy = iota
GARANTED_ON_DELAY
GARANTED
)
func ExploitPrivilegeStrategyList() []ExploitPrivilegeStrategy {
return []ExploitPrivilegeStrategy{BASIC, GARANTED_ON_DELAY, GARANTED}
}
func (t ExploitPrivilegeStrategy) String() string {
return [...]string{"NO GARANTY", "GARANTED ON SPECIFIC 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 `json:"garanted_delay_second,omitempty" bson:"garanted_delay_second,omitempty"` // GarantedDelaySecond is the garanted delay of the pricing
Exceeding bool `json:"exceeding" bson:"exceeding"` // Exceeding is the exceeding of the bill
ExceedingRatio int32 `json:"exceeding_ratio" bson:"exceeding_ratio" default:"0"` // ExceedingRatio is the exceeding ratio of the bill
}

View File

@ -0,0 +1,131 @@
package pricing
import (
"errors"
"fmt"
"strconv"
"time"
)
type BuyingStrategy int
const (
UNLIMITED BuyingStrategy = iota
SUBSCRIPTION
PAY_PER_USE
)
func (t BuyingStrategy) String() string {
return [...]string{"UNLIMITED", "SUBSCRIPTION", "PAY PER USE"}[t]
}
func BuyingStrategyList() []BuyingStrategy {
return []BuyingStrategy{UNLIMITED, 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) String() string {
return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
}
func TimePricingStrategyList() []TimePricingStrategy {
return []TimePricingStrategy{ONCE, 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")
}
type PricingStrategy[T Strategy] struct {
Price float64 `json:"price" bson:"price" default:"0"` // Price is the Price of the pricing
Currency string `json:"currency" bson:"currency" default:"USD"` // Currency is the currency 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]) 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

@ -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"
@ -9,7 +10,6 @@ import (
"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/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
resource "cloud.o-forge.io/core/oc-lib/models/resources" resource "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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
w2 "cloud.o-forge.io/core/oc-lib/models/workflow" w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/models/workflow_execution"
@ -27,15 +27,15 @@ 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.PEER.String(): func() utils.DBObject { return &peer.Peer{} }, tools.PEER.String(): func() utils.DBObject { return &peer.Peer{} },
tools.COLLABORATIVE_AREA.String(): func() utils.DBObject { return &collaborative_area.CollaborativeArea{} }, tools.COLLABORATIVE_AREA.String(): func() utils.DBObject { return &collaborative_area.CollaborativeArea{} },
tools.RULE.String(): func() utils.DBObject { return &rule.Rule{} }, tools.RULE.String(): func() utils.DBObject { return &rule.Rule{} },
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

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

@ -0,0 +1,324 @@
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/enum"
"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 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 enum.CompletionStatus `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 = enum.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.Graph.IsProcessing, request) // get the processing items
datas := scheduler.Workflow.GetPricedItem(scheduler.Workflow.Graph.IsData, request) // get the data items
storages := scheduler.Workflow.GetPricedItem(scheduler.Workflow.Graph.IsStorage, request) // get the storage items
workflows := scheduler.Workflow.GetPricedItem(scheduler.Workflow.Graph.IsWorkflow, request) // 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: enum.DRAFTED,
PeerID: peerID,
}
peerOrder.GenerateID()
for _, resource := range resources {
peerOrder.AddItem(resource, len(resources)) // 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 {
_, priceds, _, err := scheduler.Workflow.Planify(exec.ExecDate, exec.EndDate, request)
if err != nil {
return draftedBookings, errors.New("could not planify the workflow" + fmt.Sprintf("%v", err))
}
bookings := exec.Book(scheduler.Workflow.UUID, priceds)
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 != enum.PAID {
gotAnUnpaid = true
}
d.Status = enum.PARTIAL
d.SubOrders[res.GetID()] = res
if count == len(d.SubOrders) && !gotAnUnpaid {
d.Status = enum.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 enum.CompletionStatus `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 = enum.PENDING
go func() {
// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
if d.Status == enum.PAID {
for _, b := range d.Items {
if !b.Item.IsPurchased() {
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 != enum.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()
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,25 +63,25 @@ 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
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) { func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) {
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

@ -15,11 +15,11 @@ import (
* it defines the execution data * it defines the execution data
*/ */
type PeerExecution struct { type PeerExecution struct {
Method string `json:"method" bson:"method"` Method string `json:"method" bson:"method"`
Url string `json:"url" bson:"url"` Url string `json:"url" bson:"url"`
Body map[string]interface{} `json:"body" bson:"body"` Body interface{} `json:"body" bson:"body"`
DataType int `json:"data_type" bson:"data_type"` DataType int `json:"data_type" bson:"data_type"`
DataID string `json:"data_id" bson:"data_id"` DataID string `json:"data_id" bson:"data_id"`
} }
var cache = &PeerCache{} // Singleton instance of the peer cache var cache = &PeerCache{} // Singleton instance of the peer cache
@ -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
@ -70,17 +70,15 @@ func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools
return res.(*Peer), false return res.(*Peer), false
} }
url := p.urlFormat(res.(*Peer).Url, tools.PEER) + meth // Format the URL url := p.urlFormat(res.(*Peer).Url, tools.PEER) + meth // Format the 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 res.(*Peer).ServicesState = services // Update the services states of the peer
res.(*Peer).Services = 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
} }
// LaunchPeerExecution launches an execution on a peer // LaunchPeerExecution launches an execution on a peer
func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string, func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
dt tools.DataType, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) { dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) {
fmt.Println("Launching peer execution on", caller.URLS, dt, method) fmt.Println("Launching peer execution on", caller.URLS, dt, method)
methods := caller.URLS[dt] // Get the methods url of the data type methods := caller.URLS[dt] // Get the methods url of the data type
if m, ok := methods[method]; !ok || m == "" { if m, ok := methods[method]; !ok || m == "" {
@ -101,27 +99,26 @@ 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)
} }
} }
fmt.Println("URL exec", url)
return nil, p.exec(url, method, body, caller) // Execute the method return nil, p.exec(url, method, body, caller) // Execute the method
} }
// exec executes the method on the peer // exec executes the method on the peer
func (p *PeerCache) exec(url string, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) error { func (p *PeerCache) exec(url string, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) error {
var b []byte var b []byte
var err error var err error
if method == tools.POST { // Execute the POST method if it's a POST method if method == tools.POST { // Execute the POST method if it's a POST method
@ -133,8 +130,11 @@ func (p *PeerCache) exec(url string, method tools.METHOD, body map[string]interf
if method == tools.DELETE { // Execute the DELETE method if it's a DELETE method if method == tools.DELETE { // Execute the DELETE method if it's a DELETE method
b, err = caller.CallDelete(url, "") b, err = caller.CallDelete(url, "")
} }
if err != nil {
return err
}
var m map[string]interface{} var m map[string]interface{}
json.Unmarshal(b, &m) err = json.Unmarshal(b, &m)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,7 @@
package peer package peer
import ( import (
"fmt"
"strconv" "strconv"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
@ -11,31 +12,35 @@ import (
type peerMongoAccessor struct { type peerMongoAccessor 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)
overrideAuth bool
} }
// 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{ overrideAuth: true,
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
Type: tools.PEER, Type: tools.PEER,
}, },
} }
} }
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{ overrideAuth: false,
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type AbstractAccessor: utils.AbstractAccessor{
Caller: caller, Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
PeerID: peerID, Request: request,
User: username, Type: tools.PEER,
Groups: groups, // Set the caller
Type: t,
}, },
} }
} }
func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool {
return !wfa.overrideAuth
}
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
@ -62,27 +67,31 @@ 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) if i, err := strconv.Atoi(search); err == nil {
if err == nil { fmt.Println("is state search", search)
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"state": {{Operator: dbs.EQUAL.String(), Value: s}}, "state": {{Operator: dbs.EQUAL.String(), Value: i}},
}, },
} }
} else { } else {
fmt.Println("search", search)
if search == "*" {
search = ""
}
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},

View File

@ -1,7 +1,13 @@
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/enum"
"cloud.o-forge.io/core/oc-lib/models/common/models"
"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 +17,167 @@ import (
* it defines the resource compute * it defines the resource compute
*/ */
type ComputeResource struct { type ComputeResource struct {
resource_model.AbstractResource AbstractIntanciatedResource[*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 enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure
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 func (r *ComputeResource) GetType() string {
type CPU struct { return tools.COMPUTE_RESOURCE.String()
Cores uint `bson:"cores,omitempty" json:"cores,omitempty"` //TODO: validate
Architecture string `bson:"architecture,omitempty" json:"architecture,omitempty"` //TOOD: enum
Shared bool `bson:"shared,omitempty" json:"shared,omitempty"`
MinimumMemory uint `bson:"minimum_memory,omitempty" json:"minimum_memory,omitempty"`
Platform string `bson:"platform,omitempty" json:"platform,omitempty"`
} }
type RAM struct { func (abs *ComputeResource) ConvertToPricedResource(
Size uint `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"` t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
Ecc bool `bson:"ecc,omitempty" json:"ecc,omitempty"` if t != tools.COMPUTE_RESOURCE {
return nil
}
p := abs.AbstractIntanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource)
return &PricedComputeResource{
PricedResource: *priced,
}
} }
type GPU struct { type ComputeNode struct {
CudaCores uint `bson:"cuda_cores,omitempty" json:"cuda_cores,omitempty"` Name string `json:"name,omitempty" bson:"name,omitempty"`
Model string `bson:"model,omitempty" json:"model,omitempty"` Quantity int64 `json:"quantity" bson:"quantity" default:"1"`
Memory uint `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"` RAM *models.RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM
TensorCores uint `bson:"tensor_cores,omitempty" json:"tensor_cores,omitempty"` CPUs map[string]int64 `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
GPUs map[string]int64 `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
} }
type TechnologyEnum int type ComputeResourceInstance struct {
ResourceInstance[*ComputeResourcePartnership]
const ( SecurityLevel string `json:"security_level,omitempty" bson:"security_level,omitempty"`
DOCKER TechnologyEnum = iota PowerSources []string `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
KUBERNETES AnnualCO2Emissions float64 `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
SLURM CPUs map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
HW GPUs map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
CONDOR Nodes []*ComputeNode `json:"nodes,omitempty" bson:"nodes,omitempty"`
)
func (t TechnologyEnum) String() string {
return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
} }
type AccessEnum int type ComputeResourcePartnership struct {
ResourcePartnerShip[*ComputeResourcePricingProfile]
const ( MaxAllowedCPUsCores map[string]int `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"`
SSH AccessEnum = iota MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"`
SSH_KUBE_API MaxAllowedRAMSize float64 `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"`
SSH_SLURM }
SSH_DOCKER
OPENCLOUD type ComputeResourcePricingProfile struct {
VPN pricing.ExploitPricingProfile[pricing.TimePricingStrategy]
) // ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time
CPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs
func (a AccessEnum) String() string { GPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs
return [...]string{"SSH", "SSH_KUBE_API", "SSH_SLURM", "SSH_DOCKER", "OPENCLOUD", "VPN"}[a] RAMPrice float64 `json:"ram_price" bson:"ram_price" default:"-1"` // RAMPrice is the price of the RAM
}
func (p *ComputeResourcePricingProfile) IsPurchased() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE
}
func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
return -1
}
// NOT A PROPER QUANTITY
// amountOfData is the number of CPUs, GPUs or RAM dependings on the params
func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, 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.CPUsPrices[model]; ok {
p.Pricing.Price = p.CPUsPrices[model]
}
r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
if strings.Contains(params[0], "gpus") && len(params) > 1 {
if _, ok := p.GPUsPrices[model]; ok {
p.Pricing.Price = p.GPUsPrices[model]
}
r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
if strings.Contains(params[0], "ram") {
if p.RAMPrice >= 0 {
p.Pricing.Price = p.RAMPrice
}
r, err := p.Pricing.GetPrice(float64(amountOfData), explicitDuration, start, &end)
if err != nil {
return 0, err
}
pp += r
}
return pp, nil
}
type PricedComputeResource struct {
PricedResource
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 *PricedComputeResource) GetType() tools.DataType {
return tools.COMPUTE_RESOURCE
}
func (r *PricedComputeResource) GetPrice() (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("usage start and end must be set")
}
if r.SelectedPricing == nil {
return 0, errors.New("selected pricing must be set")
}
pricing := *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, "cpus", model)
if err != nil {
return 0, err
}
price += cpus
}
}
ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "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 *PricedComputeResource) 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,42 +1,169 @@
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/models"
"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"
) )
// enum of public private or licenced data
type DataLicense int
const (
PUBLIC DataLicense = iota
PRIVATE
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) AbstractIntanciatedResource[*DataInstance]
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,omitempty" bson:"size,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
}
func (r *DataResource) GetType() string {
return tools.DATA_RESOURCE.String()
}
func (abs *DataResource) ConvertToPricedResource(
t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
if t != tools.DATA_RESOURCE {
return nil
}
p := abs.AbstractIntanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource)
return &PricedDataResource{
PricedResource: *priced,
}
}
type DataInstance struct {
ResourceInstance[*DataResourcePartnership]
Source string `json:"source" bson:"source"` // Source is the source of the data
}
func (ri *DataInstance) StoreDraftDefault() {
found := false
for _, p := range ri.ResourceInstance.Outputs {
if p.Attr == "source" {
found = true
break
}
}
if !found {
ri.ResourceInstance.Outputs = append(ri.ResourceInstance.Outputs, models.Param{
Attr: "source",
Value: ri.Source,
Readonly: true,
})
}
ri.ResourceInstance.StoreDraftDefault()
}
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 (t DataResourcePricingStrategy) String() string {
return [...]string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}[t]
}
func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
return []DataResourcePricingStrategy{PER_DOWNLOAD, 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, 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 PricedDataResource struct {
PricedResource
UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
}
func (r *PricedDataResource) GetType() tools.DataType {
return tools.DATA_RESOURCE
}
func (r *PricedDataResource) GetPrice() (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("usage start and end must be set")
}
if r.SelectedPricing == nil {
return 0, errors.New("selected pricing must be set")
}
pricing := *r.SelectedPricing
var err error
amountOfData := float64(1)
if pricing.GetOverrideStrategyValue() >= 0 {
amountOfData, err = ToDataResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
if err != nil {
return 0, err
}
}
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
} }

View File

@ -0,0 +1,32 @@
package resources
import (
"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/tools"
)
type ResourceInterface interface {
utils.DBObject
Trim()
ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF
GetType() string
GetSelectedInstance() utils.DBObject
SetAllowedInstances(request *tools.APIRequest)
}
type ResourceInstanceITF interface {
utils.DBObject
GetID() string
GetName() string
StoreDraftDefault()
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups()
}
type ResourcePartnerITF interface {
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() map[string][]string
ClearPeerGroups()
}

View File

@ -0,0 +1,65 @@
package resources
import (
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
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) {
r.Clear()
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 ItemResource struct {
Data *DataResource `bson:"data,omitempty" json:"data,omitempty"`
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"`
}

View File

@ -0,0 +1,90 @@
package resources
import (
"errors"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/tools"
)
type PricedResource struct {
Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
PricingProfiles []pricing.PricingProfileITF `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"`
SelectedPricing *pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
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"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
}
func (abs *PricedResource) GetID() string {
return abs.ResourceID
}
func (abs *PricedResource) GetType() tools.DataType {
return abs.ResourceType
}
func (abs *PricedResource) GetCreatorID() string {
return abs.CreatorID
}
func (abs *PricedResource) SetStartUsage(start time.Time) {
if abs.UsageStart == nil {
abs.UsageStart = &start
}
}
func (abs *PricedResource) SetEndUsage(end time.Time) {
if abs.UsageEnd == nil {
abs.UsageEnd = &end
}
}
func (abs *PricedResource) IsPurchased() bool {
if abs.SelectedPricing == nil {
return false
}
return (*abs.SelectedPricing).IsPurchased()
}
func (abs *PricedResource) GetLocationEnd() *time.Time {
return abs.UsageEnd
}
func (abs *PricedResource) GetLocationStart() *time.Time {
return abs.UsageStart
}
func (abs *PricedResource) SetLocationStart(start time.Time) {
abs.UsageStart = &start
}
func (abs *PricedResource) SetLocationEnd(end time.Time) {
abs.UsageEnd = &end
}
func (abs *PricedResource) 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()
}
return abs.ExplicitBookingDurationS
}
func (r *PricedResource) GetPrice() (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("usage start and end must be set")
}
if r.SelectedPricing == nil {
return 0, errors.New("selected pricing must be set")
}
return (*r.SelectedPricing).GetPrice(1, 0, *r.UsageStart, *r.UsageEnd)
}

View File

@ -1,23 +1,23 @@
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/enum"
"cloud.o-forge.io/core/oc-lib/models/common/models"
"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]*models.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]*models.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 *models.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 +25,62 @@ type Expose struct {
* it defines the resource processing * it defines the resource processing
*/ */
type ProcessingResource struct { type ProcessingResource struct {
resource_model.AbstractResource AbstractIntanciatedResource[*ProcessingInstance]
IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure
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
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 { func (r *ProcessingResource) GetType() string {
return New[*ProcessingResource](tools.PROCESSING_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor return tools.PROCESSING_RESOURCE.String()
}
type ProcessingResourceAccess struct {
Container *models.Container `json:"container,omitempty" bson:"container,omitempty"` // Container is the container
}
type ProcessingInstance struct {
ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]
Access *ProcessingResourceAccess `json:"access,omitempty" bson:"access,omitempty"` // Access is the access
}
type PricedProcessingResource struct {
PricedResource
IsService bool
}
func (r *PricedProcessingResource) GetType() tools.DataType {
return tools.PROCESSING_RESOURCE
}
func (a *PricedProcessingResource) 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, 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,95 +1,189 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "slices"
"cloud.o-forge.io/core/oc-lib/config"
"cloud.o-forge.io/core/oc-lib/models/common/models"
"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/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/biter777/countries"
) )
// 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
type AbstractResource struct {
// Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource
// http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
type ResourceInterface interface { Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource
utils.DBObject ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
Trim() *resource_model.AbstractResource Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource
SetResourceModel(model *resource_model.ResourceModel) UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance
} }
type ResourceSet struct { func (r *AbstractResource) GetSelectedInstance() utils.DBObject {
Datas []string `bson:"datas,omitempty" json:"datas,omitempty"` return nil
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 *AbstractResource) GetType() string {
r.DataResources = nil return tools.INVALID.String()
r.StorageResources = nil
r.ProcessingResources = nil
r.ComputeResources = nil
r.WorkflowResources = nil
} }
func (r *ResourceSet) Fill(username string, peerID string, groups []string) { func (r *AbstractResource) StoreDraftDefault() {
for k, v := range map[utils.DBObject][]string{ r.IsDraft = true
(&DataResource{}): r.Datas, }
(&ComputeResource{}): r.Computes,
(&StorageResource{}): r.Storages, func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
(&ProcessingResource{}): r.Processings, if r.IsDraft != set.IsDrafted() && set.IsDrafted() {
(&WorkflowResource{}): r.Workflows, return true, set // only state can be updated
} { }
for _, id := range v { return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
d, _, e := k.GetAccessor(username, peerID, groups, nil).LoadOne(id) }
if e == nil {
switch k.(type) { func (r *AbstractResource) CanDelete() bool {
case *DataResource: return r.IsDraft // only draft bookings can be deleted
r.DataResources = append(r.DataResources, d.(*DataResource)) }
case *ComputeResource:
r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource)) type AbstractIntanciatedResource[T ResourceInstanceITF] struct {
case *StorageResource: AbstractResource // AbstractResource contains the basic fields of an object (id, name)
r.StorageResources = append(r.StorageResources, d.(*StorageResource)) Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
case *ProcessingResource: }
r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource))
case *WorkflowResource: func (abs *AbstractIntanciatedResource[T]) ConvertToPricedResource(
r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource)) t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
} instances := map[string]string{}
} profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances {
instances[instance.GetID()] = instance.GetName()
profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
}
return &PricedResource{
Name: abs.Name,
Logo: abs.Logo,
ResourceID: abs.UUID,
ResourceType: t,
InstancesRefs: instances,
PricingProfiles: profiles,
CreatorID: abs.CreatorID,
}
}
func (r *AbstractIntanciatedResource[T]) GetSelectedInstance() utils.DBObject {
if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
return r.Instances[*r.SelectedInstanceIndex]
}
return nil
}
func (abs *AbstractIntanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) {
abs.Instances = verifyAuthAction[T](abs.Instances, request)
}
func (d *AbstractIntanciatedResource[T]) Trim() {
d.Type = d.GetType()
if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok {
for _, instance := range d.Instances {
instance.ClearPeerGroups()
} }
} }
} }
type ItemResource struct { func (abs *AbstractIntanciatedResource[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 { func verifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
instances := []T{}
if i.Data != nil { for _, instance := range baseInstance {
return &i.Data.AbstractResource _, 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)
continue
}
for _, grp := range grps {
if slices.Contains(request.Groups, grp) {
instances = append(instances, instance)
}
}
}
}
} }
if i.Processing != nil { return instances
return &i.Processing.AbstractResource }
}
if i.Storage != nil { type GeoPoint struct {
return &i.Storage.AbstractResource Latitude float64 `json:"latitude,omitempty" bson:"latitude,omitempty"`
} Longitude float64 `json:"longitude,omitempty" bson:"longitude,omitempty"`
if i.Compute != nil { }
return &i.Compute.AbstractResource
} type ResourceInstance[T ResourcePartnerITF] struct {
if i.Workflow != nil { utils.AbstractObject
return &i.Workflow.AbstractResource Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"`
} Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
return nil AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"`
Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,omitempty"`
Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
}
func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
pricings := []pricing.PricingProfileITF{}
for _, p := range ri.Partnerships {
pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
}
return pricings
}
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 []T `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"`
}
func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
if ri.PeerGroups[peerID] != nil {
for _, p := range ri.PeerGroups[peerID] {
if slices.Contains(groups, p) {
profiles := []pricing.PricingProfileITF{}
for _, ri := range ri.PricingProfiles {
profiles = append(profiles, ri)
}
return profiles
}
}
}
return []pricing.PricingProfileITF{}
}
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
return rp.PeerGroups
}
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
rp.PeerGroups = map[string][]string{}
} }

View File

@ -1,9 +1,10 @@
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/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"
) )
@ -14,16 +15,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(), Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Request: request,
Caller: caller, Type: t,
PeerID: peerID,
User: username, // Set the caller
Groups: groups, // Set the caller
Type: t,
}, },
generateData: g, generateData: g,
} }
@ -32,19 +32,18 @@ func New[T ResourceInterface](t tools.DataType, username string, peerID string,
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (dca *resourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) { func (dca *resourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, dca) return utils.GenericDeleteOne(id, dca)
} }
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).Trim()
return utils.GenericUpdateOne(set.(T).Trim(), id, dca, dca.generateData()) // TODO CHANGE 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).Trim()
return utils.GenericStoreOne(data.(T).Trim(), dca) 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,43 +52,38 @@ 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()) d.(T).SetAllowedInstances(dca.Request)
if err == nil && len(resources) > 0 {
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
}
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())
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 { d.(T).SetAllowedInstances(wfa.Request)
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
}
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())
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 { d.(T).SetAllowedInstances(wfa.Request)
d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel))
}
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 {
if search == "*" {
search = ""
}
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.type": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractresource.owner": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractresource.source_url": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.owners.name": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
}, },
} }
} }

View File

@ -1,171 +0,0 @@
package resource_model
import (
"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/tools"
"github.com/google/uuid"
)
type WebResource struct {
Protocol string `bson:"protocol,omitempty" json:"protocol,omitempty"` // Protocol is the protocol 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 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
}
/*
* ResourceModel is a struct that represents a resource model
* it defines the resource metadata and specificity
* Warning: This struct is not user available, it is only used by the system
*/
type ResourceModel struct {
UUID string `json:"id,omitempty" bson:"id,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
Model map[string]map[string]Model `json:"model,omitempty" bson:"model,omitempty"`
}
func (ao *ResourceModel) GetID() string {
return ao.UUID
}
func (ao *ResourceModel) UpToDate(user string, create 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
}
func (d *ResourceModel) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor {
return &ResourceModelMongoAccessor{
utils.AbstractAccessor{
Type: tools.RESOURCE_MODEL,
PeerID: peerID,
Groups: groups,
User: username,
Caller: caller,
},
}
}
func (dma *ResourceModel) Deserialize(j map[string]interface{}, obj utils.DBObject) utils.DBObject {
b, err := json.Marshal(j)
if err != nil {
return nil
}
json.Unmarshal(b, obj)
return obj
}
func (dma *ResourceModel) Serialize(obj utils.DBObject) map[string]interface{} {
var m map[string]interface{}
b, err := json.Marshal(obj)
if err != nil {
return nil
}
json.Unmarshal(b, &m)
return m
}

View File

@ -1,62 +0,0 @@
package resource_model
import (
"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 ResourceModelMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}
/*
* Nothing special here, just the basic CRUD operations
*/
func New() *ResourceModelMongoAccessor {
return &ResourceModelMongoAccessor{
utils.AbstractAccessor{
Type: tools.RESOURCE_MODEL,
Logger: logs.CreateLogger(tools.RESOURCE_MODEL.String()),
},
}
}
func (wfa *ResourceModelMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, wfa)
}
func (wfa *ResourceModelMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, wfa, &ResourceModel{})
}
func (wfa *ResourceModelMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, wfa)
}
func (wfa *ResourceModelMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, wfa)
}
func (a *ResourceModelMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*ResourceModel](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *ResourceModelMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*ResourceModel](func(d utils.DBObject) utils.ShallowDBObject {
return d
}, a)
}
func (a *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*ResourceModel](filters, search,
&dbs.Filters{
Or: map[string][]dbs.Filter{
"resource_type": {{Operator: dbs.LIKE.String(), Value: search}},
},
}, func(d utils.DBObject) utils.ShallowDBObject { return d }, a)
}

View File

@ -1,61 +1,165 @@
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/enum"
"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) AbstractIntanciatedResource[*StorageResourceInstance] // AbstractResource contains the basic fields of an object (id, name)
resource_model.WebResource StorageType enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // 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
}
func (r *StorageResource) GetType() string {
return tools.STORAGE_RESOURCE.String()
}
func (abs *StorageResource) ConvertToPricedResource(
t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
if t != tools.STORAGE_RESOURCE {
return nil
}
p := abs.AbstractIntanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource)
return &PricedStorageResource{
PricedResource: *priced,
}
}
type StorageResourceInstance struct {
ResourceInstance[*StorageResourcePartnership]
Source string `bson:"source" json:"source"` // Source is the source of the storage
Local bool `bson:"local" json:"local"`
SecurityLevel string `bson:"security_level,omitempty" json:"security_level,omitempty"`
SizeType enum.StorageSize `bson:"size_type" json:"size_type" default:"0"` // SizeType is the type of the storage size
SizeGB int64 `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 PrivilegeStoragePricingStrategyList() []PrivilegeStoragePricingStrategy {
return []PrivilegeStoragePricingStrategy{BASIC_STORAGE, GARANTED_ON_DELAY_STORAGE, GARANTED_STORAGE}
}
func (t PrivilegeStoragePricingStrategy) String() string {
return [...]string{"NO MEMORY HOLDING", "KEEPED ON MEMORY GARANTED DURING DELAY", "KEEPED ON MEMORY GARANTED"}[t]
}
type StorageResourcePricingStrategy int
const (
PER_DATA_STORED StorageResourcePricingStrategy = iota
PER_TB_STORED
PER_GB_STORED
PER_MB_STORED
PER_KB_STORED
)
func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
return []StorageResourcePricingStrategy{PER_DATA_STORED, PER_TB_STORED, PER_GB_STORED, PER_MB_STORED, PER_KB_STORED}
}
func (t StorageResourcePricingStrategy) String() string {
return [...]string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}[t]
}
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, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end)
}
type PricedStorageResource struct {
PricedResource
UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
}
func (r *PricedStorageResource) GetType() tools.DataType {
return tools.STORAGE_RESOURCE
}
func (r *PricedStorageResource) GetPrice() (float64, error) {
if r.UsageStart == nil || r.UsageEnd == nil {
return 0, errors.New("usage start and end must be set")
}
if r.SelectedPricing == nil {
return 0, errors.New("selected pricing must be set")
}
pricing := *r.SelectedPricing
var err error
amountOfData := float64(1)
if pricing.GetOverrideStrategyValue() >= 0 {
amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
if err != nil {
return 0, err
}
}
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
} }

View File

@ -1,18 +1,45 @@
package resources package resources
import ( import (
"cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "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 WorkflowResourcePricingProfile struct{}
// WorkflowResource is a struct that represents a workflow resource // WorkflowResource is a struct that represents a workflow resource
// it defines the resource workflow // it defines the resource workflow
type WorkflowResource struct { type WorkflowResource struct {
resource_model.AbstractResource AbstractResource
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 { func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return New[*WorkflowResource](tools.WORKFLOW_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &WorkflowResource{} }) // Create a new instance of the accessor return NewAccessor[*ComputeResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
}
func (r *WorkflowResource) GetType() string {
return tools.WORKFLOW_RESOURCE.String()
}
func (d *WorkflowResource) Trim() {
/* EMPTY */
}
func (abs *WorkflowResource) VerifyAuth(request *tools.APIRequest) bool {
return true
}
func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */
}
func (w *WorkflowResource) ConvertToPricedResource(
t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
return &PricedResource{
Name: w.Name,
Logo: w.Logo,
ResourceID: w.UUID,
ResourceType: t,
CreatorID: w.CreatorID,
}
} }

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,18 @@ 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"` CreationDate time.Time `json:"creation_date" bson:"creation_date"`
AccessMode AccessMode `json:"access_mode" bson:"access_mode" default:"0"` 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 (ri *AbstractObject) GetAccessor(request *tools.APIRequest) Accessor {
return nil
} }
func (r *AbstractObject) GenerateID() { func (r *AbstractObject) GenerateID() {
@ -45,6 +47,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
@ -55,19 +73,27 @@ func (ao AbstractObject) GetName() string {
return ao.Name return ao.Name
} }
func (ao *AbstractObject) GetCreatorID() string {
return ao.CreatorID
}
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 {
if search == "*" {
search = ""
}
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
@ -96,171 +122,47 @@ 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) ShouldVerifyAuth() bool {
return true
}
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,158 @@
package utils package utils
/* import (
type Price struct { "errors"
Price float64 `json:"price,omitempty" bson:"price,omitempty"`
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 a.ShouldVerifyAuth() && !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 a.ShouldVerifyAuth() && !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 a.ShouldVerifyAuth() && !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 a.ShouldVerifyAuth() && !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 (a.ShouldVerifyAuth() && !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())
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 && 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

@ -11,8 +11,8 @@ type ShallowDBObject interface {
GenerateID() GenerateID()
GetID() string GetID() string
GetName() string GetName() string
Deserialize(j map[string]interface{}, obj DBObject) DBObject
Serialize(obj DBObject) map[string]interface{} Serialize(obj DBObject) map[string]interface{}
Deserialize(j map[string]interface{}, obj DBObject) DBObject
} }
// DBObject is an interface that defines the basic methods for a DBObject // DBObject is an interface that defines the basic methods for a DBObject
@ -20,26 +20,33 @@ type DBObject interface {
GenerateID() GenerateID()
GetID() string GetID() string
GetName() string GetName() string
IsDrafted() bool
CanDelete() bool
StoreDraftDefault()
GetCreatorID() string
UpToDate(user string, create bool) UpToDate(user string, create bool)
VerifyAuth(username string, PeerID string, groups []string) bool CanUpdate(set DBObject) (bool, DBObject)
Deserialize(j map[string]interface{}, obj DBObject) DBObject VerifyAuth(request *tools.APIRequest) bool
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
Deserialize(j map[string]interface{}, obj DBObject) DBObject
} }
// 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 {
GetType() tools.DataType
GetUser() string GetUser() string
GetPeerID() string GetPeerID() string
GetGroups() []string GetGroups() []string
ShouldVerifyAuth() bool
GetType() tools.DataType
GetLogger() *zerolog.Logger GetLogger() *zerolog.Logger
GetCaller() *tools.HTTPCaller GetCaller() *tools.HTTPCaller
Search(filters *dbs.Filters, search string) ([]ShallowDBObject, int, error) GetRequest() *tools.APIRequest
LoadAll() ([]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)
StoreOne(data DBObject) (DBObject, int, error) StoreOne(data DBObject) (DBObject, int, error)
LoadAll(isDraft bool) ([]ShallowDBObject, int, error)
UpdateOne(set DBObject, id string) (DBObject, int, error) UpdateOne(set DBObject, id string) (DBObject, int, error)
Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error)
} }

View File

@ -1,8 +1,9 @@
package graph package graph
import ( import (
"time"
"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,58 +14,114 @@ 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 (wf *Graph) IsProcessing(item GraphItem) bool {
if item, ok := g.Items[id]; ok { return item.Processing != nil
if item.Data != nil { }
return tools.DATA_RESOURCE.String(), item.Data
} else if item.Compute != nil { func (wf *Graph) IsCompute(item GraphItem) bool {
return tools.COMPUTE_RESOURCE.String(), item.Compute return item.Compute != nil
} else if item.Workflow != nil { }
return tools.WORKFLOW_RESOURCE.String(), item.Workflow
} else if item.Processing != nil { func (wf *Graph) IsData(item GraphItem) bool {
return tools.PROCESSING_RESOURCE.String(), item.Processing return item.Data != nil
} else if item.Storage != nil { }
return tools.STORAGE_RESOURCE.String(), item.Storage
func (wf *Graph) IsStorage(item GraphItem) bool {
return item.Storage != nil
}
func (wf *Graph) IsWorkflow(item GraphItem) bool {
return item.Workflow != nil
}
func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (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
}
priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request)
if source != "" {
if priced.GetLocationStart() != nil {
near := float64(priced.GetLocationStart().Sub(start).Seconds())
if near < nearestStart {
nearestStart = near
}
}
if priced.GetLocationEnd() != nil {
duration := float64(priced.GetLocationEnd().Sub(*priced.GetLocationStart()).Seconds())
if longestDuration < duration {
longestDuration = duration
}
} else {
oneIsInfinite = true
}
}
} }
} }
return "", nil if oneIsInfinite {
return nearestStart, -1
}
return nearestStart, longestDuration
} }
// GraphItem is a struct that represents an item in a graph /*
type GraphItem struct { * GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
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 func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 {
Height float64 `bson:"height" json:"height" validate:"required"` // Height is the graphical height of the item currents := []float64{} // list of current time
Position Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item for _, link := range g.Links { // for each link
*resources.ItemResource // ItemResource is the resource of the item affected to the item 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
}
dt, r := g.GetResource(source) // get the resource of the source
if r == nil { // if item is nil, continue
continue
}
priced := r.ConvertToPricedResource(dt, request)
current := priced.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, request) // 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
} }
// GraphLink is a struct that represents a link between two items in a graph func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
type GraphLink struct { if item, ok := g.Items[id]; ok {
Source Position `bson:"source" json:"source" validate:"required"` // Source is the source graphical position of the link if item.Data != nil {
Destination Position `bson:"destination" json:"destination" validate:"required"` // Destination is the destination graphical position of the link return tools.DATA_RESOURCE, item.Data
Style *GraphLinkStyle `bson:"style,omitempty" json:"style,omitempty"` // Style is the graphical style of the link } else if item.Compute != nil {
} return tools.COMPUTE_RESOURCE, item.Compute
} else if item.Workflow != nil {
// GraphLinkStyle is a struct that represents the style of a link in a graph return tools.WORKFLOW_RESOURCE, item.Workflow
type GraphLinkStyle struct { } else if item.Processing != nil {
Color int64 `bson:"color" json:"color"` // Color is the graphical color of the link (int description of a color, can be transpose as hex) return tools.PROCESSING_RESOURCE, item.Processing
Stroke float64 `bson:"stroke" json:"stroke"` // Stroke is the graphical stroke of the link } else if item.Storage != nil {
Tension float64 `bson:"tension" json:"tension"` // Tension is the graphical tension of the link return tools.STORAGE_RESOURCE, item.Storage
HeadRadius float64 `bson:"head_radius" json:"head_radius"` // graphical pin radius }
DashWidth float64 `bson:"dash_width" json:"dash_width"` // DashWidth is the graphical dash width of the link }
DashSpace float64 `bson:"dash_space" json:"dash_space"` // DashSpace is the graphical dash space of the link return tools.INVALID, nil
EndArrow Position `bson:"end_arrow" json:"end_arrow"` // EndArrow is the graphical end arrow of the link
StartArrow Position `bson:"start_arrow" json:"start_arrow"` // StartArrow is the graphical start arrow of the link
ArrowStyle int64 `bson:"arrow_style" json:"arrow_style"` // ArrowStyle is the graphical arrow style of the link (enum foundable in UI)
ArrowDirection int64 `bson:"arrow_direction" json:"arrow_direction"` // ArrowDirection is the graphical arrow direction of the link (enum foundable in UI)
StartArrowWidth float64 `bson:"start_arrow_width" json:"start_arrow_width"` // StartArrowWidth is the graphical start arrow width of the link
EndArrowWidth float64 `bson:"end_arrow_width" json:"end_arrow_width"` // EndArrowWidth is the graphical end arrow width of the link
}
// Position is a struct that represents a graphical position
type Position struct {
ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal), TODO: rename to ItemID
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
} }

View File

@ -0,0 +1,30 @@
package graph
import (
"cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/tools"
)
// GraphItem is a struct that represents an item in a graph
type GraphItem struct {
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
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
*resources.ItemResource // ItemResource is the resource of the item affected to the item
}
func (g *GraphItem) GetResource() (tools.DataType, resources.ResourceInterface) {
if g.Data != nil {
return tools.DATA_RESOURCE, g.Data
} else if g.Compute != nil {
return tools.COMPUTE_RESOURCE, g.Compute
} else if g.Workflow != nil {
return tools.WORKFLOW_RESOURCE, g.Workflow
} else if g.Processing != nil {
return tools.PROCESSING_RESOURCE, g.Processing
} else if g.Storage != nil {
return tools.STORAGE_RESOURCE, g.Storage
}
return tools.INVALID, nil
}

View File

@ -0,0 +1,45 @@
package graph
// GraphLink is a struct that represents a link between two items in a graph
type GraphLink struct {
Source Position `bson:"source" json:"source" validate:"required"` // Source is the source graphical position of the link
Destination Position `bson:"destination" json:"destination" validate:"required"` // Destination is the destination graphical position 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
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)
Stroke float64 `bson:"stroke" json:"stroke"` // Stroke is the graphical stroke of the link
Tension float64 `bson:"tension" json:"tension"` // Tension is the graphical tension of the link
HeadRadius float64 `bson:"head_radius" json:"head_radius"` // graphical pin radius
DashWidth float64 `bson:"dash_width" json:"dash_width"` // DashWidth is the graphical dash width of the link
DashSpace float64 `bson:"dash_space" json:"dash_space"` // DashSpace is the graphical dash space of the link
EndArrow Position `bson:"end_arrow" json:"end_arrow"` // EndArrow is the graphical end arrow of the link
StartArrow Position `bson:"start_arrow" json:"start_arrow"` // StartArrow is the graphical start arrow of the link
ArrowStyle int64 `bson:"arrow_style" json:"arrow_style"` // ArrowStyle is the graphical arrow style of the link (enum foundable in UI)
ArrowDirection int64 `bson:"arrow_direction" json:"arrow_direction"` // ArrowDirection is the graphical arrow direction of the link (enum foundable in UI)
StartArrowWidth float64 `bson:"start_arrow_width" json:"start_arrow_width"` // StartArrowWidth is the graphical start arrow width of the link
EndArrowWidth float64 `bson:"end_arrow_width" json:"end_arrow_width"` // EndArrowWidth is the graphical end arrow width of the link
}
// Position is a struct that represents a graphical position
type Position struct {
ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal)
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
}

View File

@ -2,8 +2,11 @@ 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"
"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"
@ -11,105 +14,77 @@ import (
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
/*
* AbstractWorkflow is a struct that represents a workflow for resource or native workflow
* Warning: there is 2 types of workflows, the resource workflow and the native workflow
* native workflow is the one that you create to schedule an execution
* resource workflow is the one that is created to set our native workflow in catalog
*/
type AbstractWorkflow struct {
resources.ResourceSet
Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow
}
func (w *AbstractWorkflow) GetWorkflows() (list_computings []graph.GraphItem) {
for _, item := range w.Graph.Items {
if item.Workflow != nil {
list_computings = append(list_computings, item)
}
}
return
}
func (w *AbstractWorkflow) GetComputeByRelatedProcessing(processingID string) []*resources.ComputeResource {
storages := []*resources.ComputeResource{}
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].Compute // 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].Compute // 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) GetStoragesByRelatedProcessing(processingID string) []*resources.StorageResource {
storages := []*resources.StorageResource{}
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) {
for _, item := range w.Graph.Items {
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 (w *AbstractWorkflow) isDCLink(link graph.GraphLink) (bool, string) {
if w.Graph == nil || w.Graph.Items == nil {
return false, ""
}
if d, ok := w.Graph.Items[link.Source.ID]; ok && d.Compute != nil {
return true, d.Compute.UUID
}
if d, ok := w.Graph.Items[link.Destination.ID]; ok && d.Compute != nil {
return true, d.Compute.UUID
}
return false, ""
}
/* /*
* Workflow is a struct that represents a workflow * Workflow is a struct that represents a workflow
* it defines the native workflow * it defines the native workflow
*/ */
type Workflow struct { type Workflow struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
AbstractWorkflow // AbstractWorkflow contains the basic fields of a workflow resources.ResourceSet
Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
// Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow // AbstractWorkflow contains the basic fields of a workflow
} }
func (ao *Workflow) VerifyAuth(username string, peerID string, groups []string) bool { func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}
func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) {
for _, item := range w.Graph.Items {
if f(item) {
list_datas = append(list_datas, item)
}
}
return
}
func (w *Workflow) GetPricedItem(f func(item graph.GraphItem) bool, request *tools.APIRequest) map[string]pricing.PricedItemITF {
list_datas := map[string]pricing.PricedItemITF{}
for _, item := range w.Graph.Items {
if f(item) {
dt, res := item.GetResource()
ord := res.ConvertToPricedResource(dt, request)
list_datas[res.GetID()] = ord
}
}
return list_datas
}
func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) []resources.ResourceInterface {
storages := []resources.ResourceInterface{}
for _, link := range w.Graph.Links {
nodeID := link.Destination.ID
var node resources.ResourceInterface
if g(w.Graph.Items[link.Source.ID]) {
item := w.Graph.Items[link.Source.ID]
_, node = item.GetResource()
}
if node == nil && g(w.Graph.Items[link.Destination.ID]) { // if the source is not a storage, we consider that the destination is the storage
nodeID = link.Source.ID
item := w.Graph.Items[link.Destination.ID] // and the processing is the source
_, node = item.GetResource() // we are looking for the storage as destination
}
if processingID == nodeID && node != nil { // if the storage is linked to the processing
storages = append(storages, node)
}
}
return storages
}
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
isAuthorized := false 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 +95,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
} }
@ -141,6 +116,91 @@ 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 { func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType][]pricing.PricedItemITF, *Workflow, error) {
return New(tools.WORKFLOW, username, peerID, groups, caller) // Create a new instance of the accessor priceds := map[tools.DataType][]pricing.PricedItemITF{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
}, func(started time.Time, duration float64) *time.Time {
s := started.Add(time.Duration(duration))
return &s
})
if err != nil {
return 0, priceds, nil, err
}
if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, wf.Graph.IsData,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start, 0
}, func(started time.Time, duration float64) *time.Time {
return end
}); err != nil {
return 0, priceds, nil, err
}
for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request, f,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
if f(i) {
_, r = i.GetResource()
}
return r
}, request)
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
}, func(started time.Time, duration float64) *time.Time {
s := started.Add(time.Duration(duration))
return &s
}); err != nil {
return 0, priceds, nil, err
}
}
longest := common.GetPlannerLongestTime(end, priceds, request)
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
longest := float64(-1)
r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
if code != 200 || err != nil {
return start, longest
}
if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
return start, longest
} else if neoLongest > longest {
longest = neoLongest
}
return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest
}, func(start time.Time, longest float64) *time.Time {
s := start.Add(time.Duration(longest) * time.Second)
return &s
}); err != nil {
return 0, priceds, nil, err
}
return longest, priceds, wf, nil
}
func plan[T resources.ResourceInterface](dt tools.DataType, wf *Workflow, priceds map[tools.DataType][]pricing.PricedItemITF, request *tools.APIRequest,
f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType][]pricing.PricedItemITF, error) {
resources := []T{}
for _, item := range wf.GetGraphItems(f) {
if priceds[dt] == nil {
priceds[dt] = []pricing.PricedItemITF{}
}
dt, realItem := item.GetResource()
if realItem == nil {
return resources, priceds, errors.New("could not load the processing resource")
}
priced := realItem.ConvertToPricedResource(dt, request)
started, duration := start(realItem, priced)
priced.SetLocationStart(started)
if duration >= 0 {
if e := end(started, duration); e != nil {
priced.SetLocationEnd(*e)
}
}
if e := end(started, priced.GetExplicitDurationInS()); e != nil {
priced.SetLocationEnd(*e)
}
resources = append(resources, realItem.(T))
priceds[dt] = append(priceds[dt], priced)
}
return resources, priceds, nil
} }

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/enum"
"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"
"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 enum.BookingStatus `json:"state" bson:"state" default:"0"` // TEMPORARY TODO DEFAULT 1 -> 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) r.IsDraft = false // TODO: TEMPORARY
if err != nil { r.State = enum.SCHEDULED
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: enum.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 = enum.SUCCESS
case "pending": // Pending case "pending": // Pending
wfa.State = SCHEDULED wfa.State = enum.SCHEDULED
case "running": // Running case "running": // Running
wfa.State = STARTED wfa.State = enum.STARTED
default: // Failed default: // Failed
wfa.State = FAILURE wfa.State = enum.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(wfID string, priceds map[tools.DataType][]pricing.PricedItemITF) []*booking.Booking {
booking := d.bookEach(wfID, tools.STORAGE_RESOURCE, priceds[tools.STORAGE_RESOURCE])
booking = append(booking, d.bookEach(wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])...)
return booking
}
func (d *WorkflowExecutions) bookEach(wfID string, dt tools.DataType, priceds []pricing.PricedItemITF) []*booking.Booking {
items := []*booking.Booking{}
for _, priced := range priceds {
start := d.ExecDate
if s := priced.GetLocationStart(); s != nil {
start = *s
}
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
bookingItem := &booking.Booking{
State: enum.DRAFT,
ResourceID: priced.GetID(),
ResourceType: dt,
DestPeerID: priced.GetCreatorID(),
WorkflowID: wfID,
ExecutionID: d.GetID(),
ExpectedStartDate: start,
ExpectedEndDate: &end,
}
items = append(items, bookingItem)
}
return items
}

View File

@ -1,71 +1,99 @@
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/enum"
"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 workflowExecutionMongoAccessor struct { type workflowExecutionMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor
shallow bool
} }
func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workflowExecutionMongoAccessor { func newShallowAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor {
return &workflowExecutionMongoAccessor{ return &workflowExecutionMongoAccessor{
utils.AbstractAccessor{ shallow: true,
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type AbstractAccessor: utils.AbstractAccessor{
Caller: caller, Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
PeerID: peerID, Request: request,
User: username, // Set the caller Type: tools.WORKFLOW_EXECUTION,
Groups: groups, // Set the caller },
Type: t, }
}
func NewAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor {
return &workflowExecutionMongoAccessor{
shallow: false,
AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
Request: request,
Type: tools.WORKFLOW_EXECUTION,
}, },
} }
} }
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 == enum.DRAFT && !a.shallow && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecution).State = FORGOTTEN utils.GenericDeleteOne(d.GetID(), newShallowAccessor(a.Request))
utils.GenericRawUpdateOne(d, id, a) return nil, 404, errors.New("not found")
}
if d.(*WorkflowExecutions).State == enum.SCHEDULED && !a.shallow && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecutions).State = enum.FORGOTTEN
utils.GenericRawUpdateOne(d, id, newShallowAccessor(a.Request))
} }
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, a.GetExecFilters(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 == enum.DRAFT && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecution).State = FORGOTTEN utils.GenericDeleteOne(d.GetID(), newShallowAccessor(a.Request))
utils.GenericRawUpdateOne(d, d.GetID(), a) return nil
}
if d.(*WorkflowExecutions).State == enum.SCHEDULED && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) {
d.(*WorkflowExecutions).State = enum.FORGOTTEN
utils.GenericRawUpdateOne(d, d.GetID(), newShallowAccessor(a.Request))
return d
} }
return d return d
} }
} }
func (a *workflowExecutionMongoAccessor) GetExecFilters(search string) *dbs.Filters {
return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}},
}}
}

View File

@ -0,0 +1,201 @@
package workflow_execution
import (
"errors"
"fmt"
"strings"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"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/workflow"
"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, request *tools.APIRequest) (bool, *workflow.Workflow, []*WorkflowExecutions, error) {
if request.Caller == nil && request.Caller.URLS == nil && request.Caller.URLS[tools.BOOKING] == nil || request.Caller.URLS[tools.BOOKING][tools.GET] == "" {
return false, nil, []*WorkflowExecutions{}, errors.New("no caller defined")
}
access := workflow.NewAccessor(request)
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)
longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request)
if err != nil {
return false, wf, []*WorkflowExecutions{}, err
}
ws.DurationS = longest
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(longest)*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(wfID, priceds)
for _, b := range bookings {
meth := request.Caller.URLS[tools.BOOKING][tools.GET]
meth = strings.ReplaceAll(meth, ":id", b.ResourceID)
meth = strings.ReplaceAll(meth, ":start_date", b.ExpectedStartDate.Format("2006-01-02T15:04:05"))
meth = strings.ReplaceAll(meth, ":end_date", b.ExpectedEndDate.Format("2006-01-02T15:04:05"))
request.Caller.URLS[tools.BOOKING][tools.GET] = meth
_, err := (&peer.Peer{}).LaunchPeerExecution(b.DestPeerID, b.ResourceID, tools.BOOKING, tools.GET, nil, request.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.GET]; !ok {
return nil, []*WorkflowExecutions{}, errors.New("no path found")
}
ok, wf, executions, err := ws.CheckBooking(wfID, request)
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("purge draft" + fmt.Sprintf("%v", err))
}
exec.GenerateID()
exec.StoreDraftDefault()
// Should DELETE the previous execution2
utils.GenericStoreOne(exec, NewAccessor(request))
}
return wf, executions, 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: enum.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

@ -16,17 +16,17 @@ 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 {
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)
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) 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

@ -2,7 +2,6 @@ package workspace
import ( import (
"errors" "errors"
"fmt"
"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"
@ -18,15 +17,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 +41,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 +51,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 +63,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,9 +75,9 @@ 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
d := data.(*Workspace) d := data.(*Workspace)
@ -87,35 +92,34 @@ 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)
} }
/* /*
This function is used to share the workspace with the peers This function is used to share the workspace with the peers
*/ */
func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD, caller *tools.HTTPCaller) { func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD, caller *tools.HTTPCaller) {
fmt.Println("Sharing workspace", realData, caller)
if realData == nil || realData.Shared == "" || caller == nil || caller.Disabled { if realData == nil || realData.Shared == "" || caller == nil || caller.Disabled {
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

@ -3,7 +3,6 @@ package tools
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"strings" "strings"
"cloud.o-forge.io/core/oc-lib/config" "cloud.o-forge.io/core/oc-lib/config"
@ -11,6 +10,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
@ -143,7 +149,6 @@ func (a *API) CheckRemoteAPIs(apis []DataType) (State, map[string]string, error)
continue continue
} }
json.Unmarshal(b, &resp) json.Unmarshal(b, &resp)
fmt.Println(string(b))
if resp.Data == nil { // if resp.Data == nil { //
state = REDUCED_SERVICE // If the response is empty, return reduced service state = REDUCED_SERVICE // If the response is empty, return reduced service
continue continue

View File

@ -13,13 +13,14 @@ const (
WORKFLOW WORKFLOW
WORKFLOW_EXECUTION WORKFLOW_EXECUTION
WORKSPACE WORKSPACE
RESOURCE_MODEL
PEER PEER
COLLABORATIVE_AREA COLLABORATIVE_AREA
RULE RULE
BOOKING BOOKING
WORKFLOW_HISTORY WORKFLOW_HISTORY
WORKSPACE_HISTORY WORKSPACE_HISTORY
ORDER
BUYING_STATUS
) )
var NOAPI = "" var NOAPI = ""
@ -41,13 +42,14 @@ var DefaultAPI = [...]string{
WORKFLOWAPI, WORKFLOWAPI,
NOAPI, NOAPI,
WORKSPACEAPI, WORKSPACEAPI,
CATALOGAPI,
PEERSAPI, PEERSAPI,
SHAREDAPI, SHAREDAPI,
SHAREDAPI, SHAREDAPI,
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
@ -61,13 +63,14 @@ var Str = [...]string{
"workflow", "workflow",
"workflow_execution", "workflow_execution",
"workspace", "workspace",
"resource_model",
"peer", "peer",
"collaborative_area", "collaborative_area",
"rule", "rule",
"booking", "booking",
"workflow_history", "workflow_history",
"workspace_history", "workspace_history",
"order",
"buying_status",
} }
func FromInt(i int) string { func FromInt(i int) string {
@ -86,3 +89,7 @@ func (d DataType) String() string { // String - Returns the string name of the d
func (d DataType) EnumIndex() int { func (d DataType) EnumIndex() int {
return int(d) return int(d)
} }
func DataTypeList() []DataType {
return []DataType{DATA_RESOURCE, PROCESSING_RESOURCE, STORAGE_RESOURCE, COMPUTE_RESOURCE, WORKFLOW_RESOURCE, WORKFLOW, WORKFLOW_EXECUTION, WORKSPACE, PEER, COLLABORATIVE_AREA, RULE, BOOKING, WORKFLOW_HISTORY, WORKSPACE_HISTORY, ORDER, BUYING_STATUS}
}

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
@ -89,8 +90,11 @@ func (caller *HTTPCaller) CallDelete(url string, subpath string) ([]byte, error)
} }
// CallPost calls the POST method on the HTTP server // CallPost calls the POST method on the HTTP server
func (caller *HTTPCaller) CallPost(url string, subpath string, body map[string]interface{}, types ...string) ([]byte, error) { func (caller *HTTPCaller) CallPost(url string, subpath string, body interface{}, types ...string) ([]byte, error) {
postBody, _ := json.Marshal(body) postBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
responseBody := bytes.NewBuffer(postBody) responseBody := bytes.NewBuffer(postBody)
contentType := "application/json" contentType := "application/json"
if len(types) > 0 { if len(types) > 0 {