add sets up

This commit is contained in:
mr 2025-06-20 07:51:32 +02:00
parent 938f9f1326
commit d8ccdec501
23 changed files with 277 additions and 92 deletions

View File

@ -15,12 +15,14 @@ import (
"cloud.o-forge.io/core/oc-lib/dbs/mongo" "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" "cloud.o-forge.io/core/oc-lib/models"
"cloud.o-forge.io/core/oc-lib/models/booking"
"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/compute_units" "cloud.o-forge.io/core/oc-lib/models/compute_units"
"cloud.o-forge.io/core/oc-lib/models/order" "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/purchase_resource"
"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"
@ -53,6 +55,7 @@ const (
BOOKING = tools.BOOKING BOOKING = tools.BOOKING
ORDER = tools.ORDER ORDER = tools.ORDER
COMPUTE_UNITS = tools.COMPUTE_UNITS COMPUTE_UNITS = tools.COMPUTE_UNITS
PURCHASE_RESOURCE = tools.PURCHASE_RESOURCE
) )
// will turn into standards api hostnames // will turn into standards api hostnames
@ -585,3 +588,17 @@ func (l *LibData) ToComputeUnits() *compute_units.ComputeUnits {
} }
return nil return nil
} }
func (l *LibData) ToBookings() *booking.Booking {
if l.Data.GetAccessor(nil).GetType() == tools.BOOKING {
return l.Data.(*booking.Booking)
}
return nil
}
func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
if l.Data.GetAccessor(nil).GetType() == tools.COMPUTE_UNITS {
return l.Data.(*purchase_resource.PurchaseResource)
}
return nil
}

25
models/bill/bill.go Normal file
View File

@ -0,0 +1,25 @@
package bill
import (
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
/*
* Bill is a struct that represents when emit billing
*/
type Bill struct {
utils.AbstractObject
}
func (r *Bill) StoreDraftDefault() {
r.IsDraft = true
}
func (r *Bill) CanDelete() bool {
return r.IsDraft // only draft ComputeUnits can be deleted
}
func (d *Bill) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor
}

View File

@ -0,0 +1,63 @@
package bill
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 billMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
}
// New creates a new instance of the billMongoAccessor
func NewAccessor(request *tools.APIRequest) *billMongoAccessor {
return &billMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.COMPUTE_UNITS.String()), // Create a logger with the data type
Request: request,
Type: tools.COMPUTE_UNITS,
},
}
}
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *billMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *billMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
// should verify if a source is existing...
return utils.GenericUpdateOne(set, id, a, &Bill{})
}
func (a *billMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Bill), a)
}
func (a *billMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Bill), a)
}
func (a *billMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Bill](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *billMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Bill](a.getExec(), isDraft, a)
}
func (a *billMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Bill](filters, search, (&Bill{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *billMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@ -9,7 +9,8 @@ import (
type PricedItemITF interface { type PricedItemITF interface {
GetID() string GetID() string
GetType() tools.DataType GetType() tools.DataType
IsPurchased() bool IsPurchasable() bool
IsBooked() bool
GetCreatorID() string GetCreatorID() string
GetLocationStart() *time.Time GetLocationStart() *time.Time
SetLocationStart(start time.Time) SetLocationStart(start time.Time)

View File

@ -6,7 +6,8 @@ import (
type PricingProfileITF interface { type PricingProfileITF interface {
GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
IsPurchased() bool IsPurchasable() bool
IsBooked() bool
GetOverrideStrategyValue() int GetOverrideStrategyValue() int
} }

View File

@ -7,21 +7,65 @@ import (
"time" "time"
) )
type BillingStrategy int // BAM BAM
// should except... on
const (
BILL_ONCE BillingStrategy = iota // is a permanent buying ( predictible )
BILL_PER_WEEK
BILL_PER_MONTH
BILL_PER_YEAR
)
func (t BillingStrategy) IsBillingStrategyAllowed(bs int) (BillingStrategy, bool) {
switch t {
case BILL_ONCE:
return BILL_ONCE, bs == 0
case BILL_PER_WEEK:
case BILL_PER_MONTH:
case BILL_PER_YEAR:
return t, bs != 0
}
return t, false
}
func (t BillingStrategy) String() string {
return [...]string{"BILL_ONCE", "BILL_PER_WEEK", "BILL_PER_MONTH", "BILL_PER_YEAR"}[t]
}
func BillingStrategyList() []BillingStrategy {
return []BillingStrategy{BILL_ONCE, BILL_PER_WEEK, BILL_PER_MONTH, BILL_PER_YEAR}
}
type BuyingStrategy int type BuyingStrategy int
// should except... on // should except... on
const ( const (
UNLIMITED BuyingStrategy = iota PERMANENT BuyingStrategy = iota // is a permanent buying ( predictible )
SUBSCRIPTION UNDEFINED_SUBSCRIPTION // a endless subscription ( unpredictible )
PAY_PER_USE SUBSCRIPTION // a defined subscription ( predictible )
// PAY_PER_USE // per request. ( unpredictible )
) )
func (t BuyingStrategy) String() string { func (t BuyingStrategy) String() string {
return [...]string{"UNLIMITED", "SUBSCRIPTION", "PAY PER USE"}[t] return [...]string{"PERMANENT", "UNDEFINED_SUBSCRIPTION", "SUBSCRIPTION"}[t]
}
func (t BuyingStrategy) IsBillingStrategyAllowed(bs BillingStrategy) (BillingStrategy, bool) {
switch t {
case PERMANENT:
return BILL_ONCE, bs == BILL_ONCE
case UNDEFINED_SUBSCRIPTION:
return BILL_PER_MONTH, bs != BILL_ONCE
case SUBSCRIPTION:
/*case PAY_PER_USE:
return bs, true*/
}
return bs, false
} }
func BuyingStrategyList() []BuyingStrategy { func BuyingStrategyList() []BuyingStrategy {
return []BuyingStrategy{UNLIMITED, SUBSCRIPTION, PAY_PER_USE} return []BuyingStrategy{PERMANENT, UNDEFINED_SUBSCRIPTION, SUBSCRIPTION}
} }
type Strategy interface { type Strategy interface {
@ -118,7 +162,7 @@ type PricingStrategy[T Strategy] struct {
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) { func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) {
if p.BuyingStrategy == SUBSCRIPTION { if p.BuyingStrategy == SUBSCRIPTION {
return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end) return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
} else if p.BuyingStrategy == UNLIMITED { } else if p.BuyingStrategy == PERMANENT {
return p.Price, nil return p.Price, nil
} }
return p.Price * float64(amountOfData), nil return p.Price * float64(amountOfData), nil

View File

@ -15,9 +15,9 @@ func (d DummyStrategy) GetStrategy() string { return "DUMMY" }
func (d DummyStrategy) GetStrategyValue() int { return int(d) } func (d DummyStrategy) GetStrategyValue() int { return int(d) }
func TestBuyingStrategy_String(t *testing.T) { func TestBuyingStrategy_String(t *testing.T) {
assert.Equal(t, "UNLIMITED", pricing.UNLIMITED.String()) assert.Equal(t, "UNLIMITED", pricing.PERMANENT.String())
assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String()) assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String())
assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String()) //assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String())
} }
func TestBuyingStrategyList(t *testing.T) { func TestBuyingStrategyList(t *testing.T) {
@ -116,13 +116,13 @@ func TestPricingStrategy_GetPrice(t *testing.T) {
assert.True(t, p > 0) assert.True(t, p > 0)
// UNLIMITED case // UNLIMITED case
ps.BuyingStrategy = pricing.UNLIMITED ps.BuyingStrategy = pricing.PERMANENT
p, err = ps.GetPrice(10, 0, start, &end) p, err = ps.GetPrice(10, 0, start, &end)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5.0, p) assert.Equal(t, 5.0, p)
// PAY_PER_USE case // PAY_PER_USE case
ps.BuyingStrategy = pricing.PAY_PER_USE //ps.BuyingStrategy = pricing.PAY_PER_USE
p, err = ps.GetPrice(3, 0, start, &end) p, err = ps.GetPrice(3, 0, start, &end)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 15.0, p) assert.Equal(t, 15.0, p)

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/bill"
"cloud.o-forge.io/core/oc-lib/models/compute_units" "cloud.o-forge.io/core/oc-lib/models/compute_units"
"cloud.o-forge.io/core/oc-lib/models/order" "cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
@ -40,6 +41,7 @@ var ModelsCatalog = map[string]func() utils.DBObject{
tools.ORDER.String(): func() utils.DBObject { return &order.Order{} }, tools.ORDER.String(): func() utils.DBObject { return &order.Order{} },
tools.PURCHASE_RESOURCE.String(): func() utils.DBObject { return &purchase_resource.PurchaseResource{} }, tools.PURCHASE_RESOURCE.String(): func() utils.DBObject { return &purchase_resource.PurchaseResource{} },
tools.COMPUTE_UNITS.String(): func() utils.DBObject { return &compute_units.ComputeUnits{} }, tools.COMPUTE_UNITS.String(): func() utils.DBObject { return &compute_units.ComputeUnits{} },
tools.BILL.String(): func() utils.DBObject { return &bill.Bill{} },
} }
// Model returns the model object based on the model type // Model returns the model object based on the model type

View File

@ -265,7 +265,7 @@ func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg
d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
if d.Status == enum.PAID { if d.Status == enum.PAID {
for _, b := range d.Items { for _, b := range d.Items {
if !b.Item.IsPurchased() { if !b.Item.IsPurchasable() {
continue continue
} }
accessor := purchase_resource.NewAccessor(request) accessor := purchase_resource.NewAccessor(request)

View File

@ -80,8 +80,15 @@ type ComputeResourcePricingProfile struct {
RAMPrice float64 `json:"ram_price" bson:"ram_price" default:"-1"` // RAMPrice is the price of the RAM RAMPrice float64 `json:"ram_price" bson:"ram_price" default:"-1"` // RAMPrice is the price of the RAM
} }
func (p *ComputeResourcePricingProfile) IsPurchased() bool { func (p *ComputeResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
func (p *ComputeResourcePricingProfile) IsBooked() bool {
if p.Pricing.BuyingStrategy == pricing.PERMANENT {
p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
}
return true
} }
func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int { func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {

View File

@ -137,8 +137,13 @@ func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDura
return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
} }
func (p *DataResourcePricingProfile) IsPurchased() bool { func (p *DataResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
func (p *DataResourcePricingProfile) IsBooked() bool {
// TODO WHAT ABOUT PAY PER USE... it's a complicate CASE
return p.Pricing.BuyingStrategy != pricing.PERMANENT
} }
type PricedDataResource struct { type PricedDataResource struct {

View File

@ -35,11 +35,18 @@ func (abs *PricedResource) GetCreatorID() string {
return abs.CreatorID return abs.CreatorID
} }
func (abs *PricedResource) IsPurchased() bool { func (abs *PricedResource) IsPurchasable() bool {
if abs.SelectedPricing == nil { if abs.SelectedPricing == nil {
return false return false
} }
return (abs.SelectedPricing).IsPurchased() return (abs.SelectedPricing).IsPurchasable()
}
func (abs *PricedResource) IsBooked() bool {
if abs.SelectedPricing == nil {
return false
}
return (abs.SelectedPricing).IsBooked()
} }
func (abs *PricedResource) GetLocationEnd() *time.Time { func (abs *PricedResource) GetLocationEnd() *time.Time {

View File

@ -77,8 +77,12 @@ 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 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 { func (p *ProcessingResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
func (p *ProcessingResourcePricingProfile) IsBooked() bool {
return p.Pricing.BuyingStrategy != pricing.PERMANENT
} }
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) { func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {

View File

@ -52,7 +52,8 @@ type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
} }
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF { func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
instances := map[string]string{} instances := map[string]string{}
profiles := []pricing.PricingProfileITF{} profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances { for _, instance := range abs.Instances {

View File

@ -152,8 +152,15 @@ 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 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 { func (p *StorageResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
func (p *StorageResourcePricingProfile) IsBooked() bool {
if p.Pricing.BuyingStrategy == pricing.PERMANENT {
p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
}
return true
} }
func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) { func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {

View File

@ -76,11 +76,8 @@ func TestDataResourcePricingStrategy_GetQuantity(t *testing.T) {
func TestDataResourcePricingProfile_IsPurchased(t *testing.T) { func TestDataResourcePricingProfile_IsPurchased(t *testing.T) {
profile := &resources.DataResourcePricingProfile{} profile := &resources.DataResourcePricingProfile{}
profile.Pricing.BuyingStrategy = pricing.PAY_PER_USE
assert.False(t, profile.IsPurchased())
profile.Pricing.BuyingStrategy = pricing.SUBSCRIPTION profile.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
assert.True(t, profile.IsPurchased()) assert.True(t, profile.IsPurchasable())
} }
func TestPricedDataResource_GetPrice(t *testing.T) { func TestPricedDataResource_GetPrice(t *testing.T) {

View File

@ -22,7 +22,7 @@ type MockPricingProfile struct {
ReturnCost float64 ReturnCost float64
} }
func (m *MockPricingProfile) IsPurchased() bool { func (m *MockPricingProfile) IsPurchasable() bool {
return m.Purchased return m.Purchased
} }
@ -49,13 +49,13 @@ func TestGetIDAndCreatorAndType(t *testing.T) {
func TestIsPurchased(t *testing.T) { func TestIsPurchased(t *testing.T) {
t.Run("nil selected pricing returns false", func(t *testing.T) { t.Run("nil selected pricing returns false", func(t *testing.T) {
r := &resources.PricedResource{} r := &resources.PricedResource{}
assert.False(t, r.IsPurchased()) assert.False(t, r.IsPurchasable())
}) })
t.Run("returns true if pricing profile is purchased", func(t *testing.T) { t.Run("returns true if pricing profile is purchased", func(t *testing.T) {
mock := &MockPricingProfile{Purchased: true} mock := &MockPricingProfile{Purchased: true}
r := &resources.PricedResource{SelectedPricing: mock} r := &resources.PricedResource{SelectedPricing: mock}
assert.True(t, r.IsPurchased()) assert.True(t, r.IsPurchasable())
}) })
} }

View File

@ -95,21 +95,12 @@ func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
} }
func TestProcessingResourcePricingProfile_IsPurchased(t *testing.T) { func TestProcessingResourcePricingProfile_IsPurchased(t *testing.T) {
nonPurchased := &ProcessingResourcePricingProfile{
AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
BuyingStrategy: pricing.PAY_PER_USE,
},
},
}
assert.False(t, nonPurchased.IsPurchased())
purchased := &ProcessingResourcePricingProfile{ purchased := &ProcessingResourcePricingProfile{
AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{ Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
BuyingStrategy: pricing.UNLIMITED, BuyingStrategy: pricing.PERMANENT,
}, },
}, },
} }
assert.True(t, purchased.IsPurchased()) assert.True(t, purchased.IsPurchasable())
} }

View File

@ -2,10 +2,8 @@ package resources_test
import ( import (
"testing" "testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/models" "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/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -107,43 +105,3 @@ func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) {
_, err := res.GetPrice() _, err := res.GetPrice()
assert.Error(t, err) assert.Error(t, err)
} }
func TestPricedStorageResource_GetPrice_WithPricing(t *testing.T) {
now := time.Now()
end := now.Add(2 * time.Hour)
profile := &resources.StorageResourcePricingProfile{
ExploitPricingProfile: pricing.ExploitPricingProfile[resources.StorageResourcePricingStrategy]{
AccessPricingProfile: pricing.AccessPricingProfile[resources.StorageResourcePricingStrategy]{
Pricing: pricing.PricingStrategy[resources.StorageResourcePricingStrategy]{
BuyingStrategy: pricing.PAY_PER_USE,
Price: 42.0,
},
},
},
}
res := &resources.PricedStorageResource{
PricedResource: resources.PricedResource{
UsageStart: &now,
UsageEnd: &end,
PricingProfiles: []pricing.PricingProfileITF{profile},
},
UsageStorageGB: 1.0,
}
price, err := res.GetPrice()
assert.NoError(t, err)
assert.Equal(t, 42.0, price)
}
func TestStorageResourcePricingProfile_IsPurchased(t *testing.T) {
p := &resources.StorageResourcePricingProfile{
ExploitPricingProfile: pricing.ExploitPricingProfile[resources.StorageResourcePricingStrategy]{
AccessPricingProfile: pricing.AccessPricingProfile[resources.StorageResourcePricingStrategy]{
Pricing: pricing.PricingStrategy[resources.StorageResourcePricingStrategy]{BuyingStrategy: pricing.PAY_PER_USE},
},
},
}
assert.False(t, p.IsPurchased())
p.Pricing.BuyingStrategy = pricing.UNLIMITED
assert.True(t, p.IsPurchased())
}

View File

@ -169,8 +169,8 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) { func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
priceds := map[tools.DataType]map[string]pricing.PricedItemITF{} priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request,
buyingStrategy, pricingStrategy, buyingStrategy, pricingStrategy, wf.Graph.IsProcessing,
wf.Graph.IsProcessing, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request, buyingStrategy, pricingStrategy)) * time.Second), priced.GetExplicitDurationInS() return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request, buyingStrategy, pricingStrategy)) * time.Second), priced.GetExplicitDurationInS()
}, func(started time.Time, duration float64) *time.Time { }, func(started time.Time, duration float64) *time.Time {
s := started.Add(time.Duration(duration)) s := started.Add(time.Duration(duration))

View File

@ -8,6 +8,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/booking" "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/enum"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"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/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"
@ -21,6 +22,7 @@ import (
*/ */
type WorkflowExecution struct { type WorkflowExecution 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)
PeerBuyByGraph map[string]map[string][]string `json:"peer_buy_by_graph,omitempty" bson:"peer_buy_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id
PeerBookByGraph map[string]map[string][]string `json:"peer_book_by_graph,omitempty" bson:"peer_book_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id PeerBookByGraph map[string]map[string][]string `json:"peer_book_by_graph,omitempty" bson:"peer_book_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty"` ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty"`
ExecDate time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required ExecDate time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required
@ -110,10 +112,51 @@ func (d *WorkflowExecution) VerifyAuth(request *tools.APIRequest) bool {
} }
/* /*
booking is an activity reserved for not a long time investment. booking is an activity reserved for not a long time investment.
... purchase is dependant of a one time buying. ... purchase is dependant of a one time buying.
use of a datacenter or storage can't be buy for permanent. use of a datacenter or storage can't be buy for permanent access.
*/ */
func (d *WorkflowExecution) Buy(bs pricing.BillingStrategy, executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource {
purchases := d.buyEach(bs, executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])
purchases = append(purchases, d.buyEach(bs, executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...)
return purchases
}
func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID string, wfID string, dt tools.DataType, priceds map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource {
items := []*purchase_resource.PurchaseResource{}
for itemID, priced := range priceds {
if !priced.IsPurchasable() && bs == pricing.BILL_ONCE { // buy only that must be buy
continue
}
if d.PeerBuyByGraph == nil {
d.PeerBuyByGraph = map[string]map[string][]string{}
}
if d.PeerBuyByGraph[priced.GetCreatorID()] == nil {
d.PeerBuyByGraph[priced.GetCreatorID()] = map[string][]string{}
}
if d.PeerBuyByGraph[priced.GetCreatorID()][itemID] == nil {
d.PeerBuyByGraph[priced.GetCreatorID()][itemID] = []string{}
}
start := d.ExecDate
if s := priced.GetLocationStart(); s != nil {
start = *s
}
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,
},
ResourceID: priced.GetID(),
ResourceType: dt,
EndDate: &end,
}
items = append(items, bookingItem)
d.PeerBuyByGraph[priced.GetCreatorID()][itemID] = append(
d.PeerBuyByGraph[priced.GetCreatorID()][itemID], bookingItem.GetID())
}
return items
}
func (d *WorkflowExecution) Book(executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*booking.Booking { func (d *WorkflowExecution) Book(executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*booking.Booking {
booking := d.bookEach(executionsID, wfID, tools.STORAGE_RESOURCE, priceds[tools.STORAGE_RESOURCE]) booking := d.bookEach(executionsID, wfID, tools.STORAGE_RESOURCE, priceds[tools.STORAGE_RESOURCE])
@ -126,6 +169,9 @@ func (d *WorkflowExecution) Book(executionsID string, wfID string, priceds map[t
func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.DataType, priceds map[string]pricing.PricedItemITF) []*booking.Booking { func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.DataType, priceds map[string]pricing.PricedItemITF) []*booking.Booking {
items := []*booking.Booking{} items := []*booking.Booking{}
for itemID, priced := range priceds { for itemID, priced := range priceds {
if !priced.IsBooked() { // book only that must be booked
continue
}
if d.PeerBookByGraph == nil { if d.PeerBookByGraph == nil {
d.PeerBookByGraph = map[string]map[string][]string{} d.PeerBookByGraph = map[string]map[string][]string{}
} }

View File

@ -11,6 +11,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/enum" "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/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/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow" "cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
@ -34,8 +35,9 @@ type WorkflowSchedule struct {
DurationS float64 `json:"duration_s" default:"-1"` // End is the end time of the schedule 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 Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task
SelectedBuyingStrategy pricing.BuyingStrategy `json:"selected_buying_strategy"` SelectedBuyingStrategy pricing.BuyingStrategy `json:"selected_buying_strategy"`
SelectedPricingStrategy int `json:"selected_pricing_strategy"` SelectedPricingStrategy int `json:"selected_pricing_strategy"`
SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
} }
func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule { func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule {
@ -79,8 +81,10 @@ func (ws *WorkflowSchedule) CheckBooking(wfID string, request *tools.APIRequest)
if err != nil { if err != nil {
return false, wf, []*WorkflowExecution{}, []*booking.Booking{}, err return false, wf, []*WorkflowExecution{}, []*booking.Booking{}, err
} }
purchased := []*purchase_resource.PurchaseResource{}
bookings := []*booking.Booking{} bookings := []*booking.Booking{}
for _, exec := range execs { for _, exec := range execs {
purchased = append(purchased, exec.Buy(ws.SelectedBillingStrategy, ws.UUID, wfID, priceds)...)
bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...) bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...)
} }

View File

@ -27,6 +27,7 @@ const (
ADMIRALTY_KUBECONFIG ADMIRALTY_KUBECONFIG
ADMIRALTY_NODES ADMIRALTY_NODES
COMPUTE_UNITS COMPUTE_UNITS
BILL
) )
var NOAPI = "" var NOAPI = ""
@ -67,6 +68,7 @@ var DefaultAPI = [...]string{
ADMIRALTY_KUBECONFIGAPI, ADMIRALTY_KUBECONFIGAPI,
ADMIRALTY_NODESAPI, ADMIRALTY_NODESAPI,
DATACENTERAPI, DATACENTERAPI,
NOAPI,
} }
// Bind the standard data name to the data type // Bind the standard data name to the data type
@ -94,6 +96,7 @@ var Str = [...]string{
"admiralty_kubeconfig", "admiralty_kubeconfig",
"admiralty_node", "admiralty_node",
"compute_units", "compute_units",
"bill",
} }
func FromInt(i int) string { func FromInt(i int) string {
@ -114,5 +117,7 @@ func (d DataType) EnumIndex() int {
} }
func DataTypeList() []DataType { 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, PURCHASE_RESOURCE, ADMIRALTY_SOURCE, ADMIRALTY_TARGET, ADMIRALTY_SECRET, ADMIRALTY_KUBECONFIG, ADMIRALTY_NODES} 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, PURCHASE_RESOURCE, ADMIRALTY_SOURCE, ADMIRALTY_TARGET, ADMIRALTY_SECRET, ADMIRALTY_KUBECONFIG, ADMIRALTY_NODES, COMPUTE_UNITS, BILL}
} }