25 Commits

Author SHA1 Message Date
188b758f7a Ajouter .gitattributes 2025-11-01 16:39:25 +01:00
pb
f4b0cf5683 changed the value used as the key in the related map, using the 'node id' was erasing some relations when a a processing is linked to two storages 2025-08-08 16:15:53 +02:00
pb
e7a71188a3 changed the value used as the key in the related map, using the 'node id' was erasing some relations when a a processing is linked to two storages 2025-08-08 16:05:36 +02:00
pb
40a61387b9 added the UUID of the peer when an error appears 2025-08-05 13:39:21 +02:00
pb
cc939451fd added the UUID of the peer when an error appears 2025-08-05 13:25:47 +02:00
pb
76e9b2562e added the enum to reach the /minio/secet route 2025-08-05 11:56:27 +02:00
pb
cc3091d401 added the function to load one ressource for each ressource type 2025-07-31 15:53:05 +02:00
pb
3ddbf1a967 added comments 2025-07-30 18:28:19 +02:00
pb
be2a1cc114 corrction of the non initialized map 2025-07-30 18:21:09 +02:00
pb
a093369dc5 corrction of the non initialized map 2025-07-30 18:15:55 +02:00
pb
76d83878eb added a method to workflow which allows to retrieve items by resource type and resource id 2025-07-30 18:01:23 +02:00
pb
e735f78e58 added a new method to create kubernetes names with the same naming 2025-07-15 14:58:19 +02:00
pb
98a2359c9d added a tweak to PeerItemOrder GetPrice in order to not crash on nil Purchase 2025-07-10 11:47:54 +02:00
pb
83e590d4e1 added error handling 2025-07-09 17:42:37 +02:00
pb
4e3ff9aa08 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-09 16:54:37 +02:00
pb
424d523c5e added the bson tag for DestPeerId which was causing error when deserializing from mongo results 2025-07-09 16:50:41 +02:00
mr
346275e12c test 2025-07-08 13:59:55 +02:00
mr
6ab774cc43 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib into main
test
2025-07-08 13:42:28 +02:00
mr
2748b59221 test booking 2025-07-08 13:42:13 +02:00
pb
34f01e9740 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-08 12:02:34 +02:00
pb
dcdc6ff1d9 added more info in GenericStoreOne error generated by validate() 2025-07-08 12:02:18 +02:00
pb
365b924e4b Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-07 16:30:58 +02:00
pb
e7e56d1859 commented the PricingProfile check for IsBooked 2025-07-07 16:30:29 +02:00
mr
443546027b Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib into main 2025-07-04 10:44:59 +02:00
mr
1c4f3f756f test 2025-07-04 10:44:14 +02:00
14 changed files with 163 additions and 33 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Force Go as the main language
*.go linguist-detectable=true
* linguist-language=Go

View File

@@ -597,3 +597,65 @@ func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
}
return nil
}
// ============== ADMIRALTY ==============
// Returns a concatenation of the peerId and namespace in order for
// kubernetes ressources to have a unique name, under 63 characters
// and yet identify which peer they are created for
func GetConcatenatedName(peerId string, namespace string) string {
s := strings.Split(namespace, "-")[:2]
n := s[0] + "-" + s[1]
return peerId + "-" + n
}
// ------------- Loading resources ----------
func LoadOneStorage(storageId string, user string, peerID string, groups []string) (*resources.StorageResource, error) {
res := NewRequest(LibDataEnum(STORAGE_RESOURCE), user, peerID, groups,nil).LoadOne(storageId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading storage ressource " + storageId)
return nil,fmt.Errorf(res.Err)
}
return res.ToStorageResource(), nil
}
func LoadOneComputing(computingId string, user string, peerID string, groups []string) (*resources.ComputeResource, error) {
res := NewRequest(LibDataEnum(COMPUTE_RESOURCE), user, peerID, groups,nil).LoadOne(computingId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading computing ressource " + computingId)
return nil,fmt.Errorf(res.Err)
}
return res.ToComputeResource(), nil
}
func LoadOneProcessing(processingId string, user string, peerID string, groups []string) (*resources.ProcessingResource, error) {
res := NewRequest(LibDataEnum(PROCESSING_RESOURCE), user, peerID, groups,nil).LoadOne(processingId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading processing ressource " + processingId)
return nil,fmt.Errorf(res.Err)
}
return res.ToProcessingResource(), nil
}
func LoadOneData(dataId string, user string, peerID string, groups []string) (*resources.DataResource, error) {
res := NewRequest(LibDataEnum(DATA_RESOURCE), user, peerID, groups,nil).LoadOne(dataId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading data ressource " + dataId)
return nil,fmt.Errorf(res.Err)
}
return res.ToDataResource(), nil
}

View File

@@ -1,14 +1,16 @@
package bill
import (
"encoding/json"
"fmt"
"sync"
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"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/resources"
"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/tools"
@@ -135,20 +137,25 @@ type PeerOrder struct {
}
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.IsPurchasable() {
var priced *resources.PricedResource
bb, _ := json.Marshal(b.Item)
json.Unmarshal(bb, priced)
if !priced.IsPurchasable() {
continue
}
accessor := purchase_resource.NewAccessor(request)
accessor.StoreOne(&purchase_resource.PurchaseResource{
ResourceID: b.Item.GetID(),
ResourceType: b.Item.GetType(),
EndDate: b.Item.GetLocationEnd(),
ResourceID: priced.GetID(),
ResourceType: priced.GetType(),
EndDate: priced.GetLocationEnd(),
})
}
}
@@ -174,14 +181,26 @@ func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
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"`
Item map[string]interface{} `json:"item,omitempty" bson:"item,omitempty"`
}
func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
/////////// Temporary in order to allow GenerateOrder to complete while billing is still WIP
if d.Purchase == nil {
return 0, nil
}
///////////
var priced *resources.PricedResource
b, _ := json.Marshal(d.Item)
err := json.Unmarshal(b, priced)
if err != nil {
fmt.Println(err)
return 0, err
}
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()}},
"resource_id": {{Operator: dbs.EQUAL.String(), Value: priced.GetID()}},
},
}, "", d.Purchase.IsDraft)
if code == 200 && len(search) > 0 {
@@ -191,7 +210,7 @@ func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
}
}
}
p, err := d.Item.GetPrice()
p, err := priced.GetPrice()
if err != nil {
return 0, err
}

View File

@@ -6,7 +6,6 @@ import (
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/common/models"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -16,14 +15,14 @@ import (
* Booking is a struct that represents a booking
*/
type Booking struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
PricedItem pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error
ResumeMetrics map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"`
ResumeMetrics map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"`
ExecutionMetrics map[string][]models.MetricsSnapshot `json:"metrics,omitempty" bson:"metrics,omitempty"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
DestPeerID string `json:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer
DestPeerID string `json:"dest_peer_id,omitempty" bson:"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

View File

@@ -93,6 +93,10 @@ func (t TimePricingStrategy) String() string {
return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
}
func TimePricingStrategyListStr() []string {
return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
}
func TimePricingStrategyList() []TimePricingStrategy {
return []TimePricingStrategy{ONCE, PER_SECOND, PER_MINUTE, PER_HOUR, PER_DAY, PER_WEEK, PER_MONTH}
}

View File

@@ -89,7 +89,9 @@ const (
)
func (t DataResourcePricingStrategy) String() string {
return [...]string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}[t]
l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}...)
return l[t]
}
func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
@@ -101,7 +103,9 @@ func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
}
func (t DataResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DOWNLOAD", "PER_GB", "PER_MB", "PER_KB"}[t]
l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
}
func (t DataResourcePricingStrategy) GetStrategyValue() int {

View File

@@ -46,6 +46,7 @@ func (abs *PricedResource) IsPurchasable() bool {
}
func (abs *PricedResource) IsBooked() bool {
return true // For dev purposes, prevent that DB objects that don't have a Pricing are considered as not booked
if abs.SelectedPricing == nil {
return false
}

View File

@@ -3,19 +3,18 @@ package purchase_resource
import (
"time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
type PurchaseResource struct {
utils.AbstractObject
DestPeerID string
PricedItem pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
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"`
DestPeerID string `json:"dest_peer_id" bson:"dest_peer_id"`
PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
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 {

View File

@@ -115,11 +115,15 @@ func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
}
func (t StorageResourcePricingStrategy) String() string {
return [...]string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}[t]
l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
}
func (t StorageResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DATA_STORED", "PER_GB_STORED", "PER_MB_STORED", "PER_KB_STORED"}[t]
l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
}
func (t StorageResourcePricingStrategy) GetStrategyValue() int {

View File

@@ -49,7 +49,7 @@ func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
}
err := validate.Struct(data)
if err != nil {
return nil, 422, err
return nil, 422, errors.New("error when validating the received struct: " + err.Error())
}
id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
if err != nil {

View File

@@ -110,13 +110,11 @@ func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph
_, node = item.GetResource() // we are looking for the storage as destination
}
if processingID == nodeID && node != nil { // if the storage is linked to the processing
if _, ok := related[processingID]; !ok {
related[processingID] = Related{}
}
rel := related[node.GetID()]
relID := node.GetID()
rel := Related{}
rel.Node = node
rel.Links = append(rel.Links, link)
related[processingID] = rel
related[relID] = rel
}
}
return related
@@ -227,6 +225,33 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
return longest, priceds, wf, nil
}
// Returns a map of DataType (processing,computing,data,storage,worfklow) where each resource (identified by its UUID)
// is mapped to the list of its items (different appearance) in the graph
// ex: if the same Minio storage is represented by several nodes in the graph, in [tools.STORAGE_RESSOURCE] its UUID will be mapped to
// the list of GraphItem ID that correspond to the ID of each node
func (w *Workflow) GetItemsByResources() (map[tools.DataType]map[string][]string) {
res := make(map[tools.DataType]map[string][]string)
dtMethodMap := map[tools.DataType]func() []graph.GraphItem{
tools.STORAGE_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsStorage) },
tools.DATA_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsData) },
tools.COMPUTE_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsCompute) },
tools.PROCESSING_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsProcessing) },
tools.WORKFLOW_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsWorkflow) },
}
for dt, meth := range dtMethodMap {
res[dt] = make(map[string][]string)
items := meth()
for _, i := range items {
_, r := i.GetResource()
rId := r.GetID()
res[dt][rId] = append(res[dt][rId],i.ID)
}
}
return res
}
func plan[T resources.ResourceInterface](
dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]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]map[string]pricing.PricedItemITF, error) {

View File

@@ -1,6 +1,7 @@
package workflow_execution
import (
"encoding/json"
"strings"
"time"
@@ -141,13 +142,16 @@ func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID str
if s := priced.GetLocationStart(); s != nil {
start = *s
}
var m map[string]interface{}
b, _ := json.Marshal(priced)
json.Unmarshal(b, &m)
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
bookingItem := &purchase_resource.PurchaseResource{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID,
},
PricedItem: priced,
PricedItem: m,
ExecutionsID: executionsID,
DestPeerID: priced.GetCreatorID(),
ResourceID: priced.GetID(),
@@ -189,12 +193,15 @@ func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.
start = *s
}
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
var m map[string]interface{}
b, _ := json.Marshal(priced)
json.Unmarshal(b, &m)
bookingItem := &booking.Booking{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID,
},
PricedItem: priced,
PricedItem: m,
ExecutionsID: executionsID,
State: enum.SCHEDULED,
ResourceID: priced.GetID(),

View File

@@ -142,7 +142,7 @@ func getBooking(b *booking.Booking, request *tools.APIRequest, errCh chan error,
_, err = (&peer.Peer{}).LaunchPeerExecution(b.DestPeerID, b.ResourceID, tools.BOOKING, tools.GET, nil, &c)
if err != nil {
errCh <- err
errCh <- fmt.Errorf("error on " + b.DestPeerID + err.Error())
return
}

View File

@@ -30,6 +30,7 @@ const (
LIVE_STORAGE
BILL
MINIO_SVCACC
MINIO_SVCACC_SECRET
)
var NOAPI = ""
@@ -75,6 +76,7 @@ var DefaultAPI = [...]string{
DATACENTERAPI,
NOAPI,
MINIO,
MINIO,
}
// Bind the standard data name to the data type
@@ -105,6 +107,7 @@ var Str = [...]string{
"live_storage",
"bill",
"service_account",
"secret",
}
func FromInt(i int) string {