diff --git a/entrypoint.go b/entrypoint.go index cb83d75..3c8ddf6 100644 --- a/entrypoint.go +++ b/entrypoint.go @@ -15,12 +15,14 @@ import ( "cloud.o-forge.io/core/oc-lib/dbs/mongo" "cloud.o-forge.io/core/oc-lib/logs" "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/rules/rule" "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/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" w2 "cloud.o-forge.io/core/oc-lib/models/workflow" "cloud.o-forge.io/core/oc-lib/models/workflow_execution" @@ -53,6 +55,7 @@ const ( BOOKING = tools.BOOKING ORDER = tools.ORDER COMPUTE_UNITS = tools.COMPUTE_UNITS + PURCHASE_RESOURCE = tools.PURCHASE_RESOURCE ) // will turn into standards api hostnames @@ -585,3 +588,17 @@ func (l *LibData) ToComputeUnits() *compute_units.ComputeUnits { } 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 +} diff --git a/models/bill/bill.go b/models/bill/bill.go new file mode 100644 index 0000000..760fcc5 --- /dev/null +++ b/models/bill/bill.go @@ -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 +} diff --git a/models/bill/bill_mongo_accessor.go b/models/bill/bill_mongo_accessor.go new file mode 100644 index 0000000..5141ef0 --- /dev/null +++ b/models/bill/bill_mongo_accessor.go @@ -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 + } +} diff --git a/models/common/pricing/interfaces.go b/models/common/pricing/interfaces.go index a7605f9..fdbc62d 100755 --- a/models/common/pricing/interfaces.go +++ b/models/common/pricing/interfaces.go @@ -9,7 +9,8 @@ import ( type PricedItemITF interface { GetID() string GetType() tools.DataType - IsPurchased() bool + IsPurchasable() bool + IsBooked() bool GetCreatorID() string GetLocationStart() *time.Time SetLocationStart(start time.Time) diff --git a/models/common/pricing/pricing_profile.go b/models/common/pricing/pricing_profile.go index fa81629..79aacb5 100755 --- a/models/common/pricing/pricing_profile.go +++ b/models/common/pricing/pricing_profile.go @@ -6,7 +6,8 @@ import ( type PricingProfileITF interface { GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) - IsPurchased() bool + IsPurchasable() bool + IsBooked() bool GetOverrideStrategyValue() int } diff --git a/models/common/pricing/pricing_strategy.go b/models/common/pricing/pricing_strategy.go index 94576bb..fee356f 100755 --- a/models/common/pricing/pricing_strategy.go +++ b/models/common/pricing/pricing_strategy.go @@ -7,21 +7,65 @@ import ( "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 // should except... on const ( - UNLIMITED BuyingStrategy = iota - SUBSCRIPTION - PAY_PER_USE + PERMANENT BuyingStrategy = iota // is a permanent buying ( predictible ) + UNDEFINED_SUBSCRIPTION // a endless subscription ( unpredictible ) + SUBSCRIPTION // a defined subscription ( predictible ) + // PAY_PER_USE // per request. ( unpredictible ) ) 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 { - return []BuyingStrategy{UNLIMITED, SUBSCRIPTION, PAY_PER_USE} + return []BuyingStrategy{PERMANENT, UNDEFINED_SUBSCRIPTION, SUBSCRIPTION} } 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) { if p.BuyingStrategy == SUBSCRIPTION { 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 * float64(amountOfData), nil diff --git a/models/common/pricing/tests/pricing_test.go b/models/common/pricing/tests/pricing_test.go index 105b5d1..2aee55f 100644 --- a/models/common/pricing/tests/pricing_test.go +++ b/models/common/pricing/tests/pricing_test.go @@ -15,9 +15,9 @@ func (d DummyStrategy) GetStrategy() string { return "DUMMY" } func (d DummyStrategy) GetStrategyValue() int { return int(d) } 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, "PAY PER USE", pricing.PAY_PER_USE.String()) + //assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String()) } func TestBuyingStrategyList(t *testing.T) { @@ -116,13 +116,13 @@ func TestPricingStrategy_GetPrice(t *testing.T) { assert.True(t, p > 0) // UNLIMITED case - ps.BuyingStrategy = pricing.UNLIMITED + ps.BuyingStrategy = pricing.PERMANENT p, err = ps.GetPrice(10, 0, start, &end) assert.NoError(t, err) assert.Equal(t, 5.0, p) // PAY_PER_USE case - ps.BuyingStrategy = pricing.PAY_PER_USE + //ps.BuyingStrategy = pricing.PAY_PER_USE p, err = ps.GetPrice(3, 0, start, &end) assert.NoError(t, err) assert.Equal(t, 15.0, p) diff --git a/models/models.go b/models/models.go index 41fdffb..8674820 100644 --- a/models/models.go +++ b/models/models.go @@ -2,6 +2,7 @@ package models import ( "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/order" "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.PURCHASE_RESOURCE.String(): func() utils.DBObject { return &purchase_resource.PurchaseResource{} }, 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 diff --git a/models/order/order.go b/models/order/order.go index 585ce1e..1a90111 100644 --- a/models/order/order.go +++ b/models/order/order.go @@ -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 if d.Status == enum.PAID { for _, b := range d.Items { - if !b.Item.IsPurchased() { + if !b.Item.IsPurchasable() { continue } accessor := purchase_resource.NewAccessor(request) diff --git a/models/resources/compute.go b/models/resources/compute.go index d6ff81b..fb3ab95 100755 --- a/models/resources/compute.go +++ b/models/resources/compute.go @@ -80,8 +80,15 @@ type ComputeResourcePricingProfile struct { 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) IsPurchasable() bool { + 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 { diff --git a/models/resources/data.go b/models/resources/data.go index e9ec3df..b0c47ac 100755 --- a/models/resources/data.go +++ b/models/resources/data.go @@ -137,8 +137,13 @@ func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDura return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) } -func (p *DataResourcePricingProfile) IsPurchased() bool { - return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +func (p *DataResourcePricingProfile) IsPurchasable() bool { + 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 { diff --git a/models/resources/priced_resource.go b/models/resources/priced_resource.go index 5265cce..c65f697 100755 --- a/models/resources/priced_resource.go +++ b/models/resources/priced_resource.go @@ -35,11 +35,18 @@ func (abs *PricedResource) GetCreatorID() string { return abs.CreatorID } -func (abs *PricedResource) IsPurchased() bool { +func (abs *PricedResource) IsPurchasable() bool { if abs.SelectedPricing == nil { 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 { diff --git a/models/resources/processing.go b/models/resources/processing.go index c2f9e38..4661689 100755 --- a/models/resources/processing.go +++ b/models/resources/processing.go @@ -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 } -func (p *ProcessingResourcePricingProfile) IsPurchased() bool { - return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +func (p *ProcessingResourcePricingProfile) IsPurchasable() bool { + 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) { diff --git a/models/resources/resource.go b/models/resources/resource.go index c57a23d..45a5a62 100755 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -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 } -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{} profiles := []pricing.PricingProfileITF{} for _, instance := range abs.Instances { diff --git a/models/resources/storage.go b/models/resources/storage.go index 481f326..14d578c 100755 --- a/models/resources/storage.go +++ b/models/resources/storage.go @@ -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 } -func (p *StorageResourcePricingProfile) IsPurchased() bool { - return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +func (p *StorageResourcePricingProfile) IsPurchasable() bool { + 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) { diff --git a/models/resources/tests/data_test.go b/models/resources/tests/data_test.go index 0665519..af7cc2e 100644 --- a/models/resources/tests/data_test.go +++ b/models/resources/tests/data_test.go @@ -76,11 +76,8 @@ func TestDataResourcePricingStrategy_GetQuantity(t *testing.T) { func TestDataResourcePricingProfile_IsPurchased(t *testing.T) { profile := &resources.DataResourcePricingProfile{} - profile.Pricing.BuyingStrategy = pricing.PAY_PER_USE - assert.False(t, profile.IsPurchased()) - profile.Pricing.BuyingStrategy = pricing.SUBSCRIPTION - assert.True(t, profile.IsPurchased()) + assert.True(t, profile.IsPurchasable()) } func TestPricedDataResource_GetPrice(t *testing.T) { diff --git a/models/resources/tests/priced_resource_test.go b/models/resources/tests/priced_resource_test.go index 61616a8..d47d26e 100644 --- a/models/resources/tests/priced_resource_test.go +++ b/models/resources/tests/priced_resource_test.go @@ -22,7 +22,7 @@ type MockPricingProfile struct { ReturnCost float64 } -func (m *MockPricingProfile) IsPurchased() bool { +func (m *MockPricingProfile) IsPurchasable() bool { return m.Purchased } @@ -49,13 +49,13 @@ func TestGetIDAndCreatorAndType(t *testing.T) { func TestIsPurchased(t *testing.T) { t.Run("nil selected pricing returns false", func(t *testing.T) { 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) { mock := &MockPricingProfile{Purchased: true} r := &resources.PricedResource{SelectedPricing: mock} - assert.True(t, r.IsPurchased()) + assert.True(t, r.IsPurchasable()) }) } diff --git a/models/resources/tests/processing_test.go b/models/resources/tests/processing_test.go index 06e12c1..ce3de34 100644 --- a/models/resources/tests/processing_test.go +++ b/models/resources/tests/processing_test.go @@ -95,21 +95,12 @@ func TestProcessingResourcePricingProfile_GetPrice(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{ AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{ - BuyingStrategy: pricing.UNLIMITED, + BuyingStrategy: pricing.PERMANENT, }, }, } - assert.True(t, purchased.IsPurchased()) + assert.True(t, purchased.IsPurchasable()) } diff --git a/models/resources/tests/storage_test.go b/models/resources/tests/storage_test.go index 1a42978..2e1d0e2 100644 --- a/models/resources/tests/storage_test.go +++ b/models/resources/tests/storage_test.go @@ -2,10 +2,8 @@ package resources_test import ( "testing" - "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/tools" "github.com/stretchr/testify/assert" @@ -107,43 +105,3 @@ func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) { _, err := res.GetPrice() 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()) -} diff --git a/models/workflow/workflow.go b/models/workflow/workflow.go index 5c0b134..d140442 100644 --- a/models/workflow/workflow.go +++ b/models/workflow/workflow.go @@ -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) { priceds := map[tools.DataType]map[string]pricing.PricedItemITF{} ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, - buyingStrategy, pricingStrategy, - wf.Graph.IsProcessing, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { + buyingStrategy, pricingStrategy, 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, buyingStrategy, pricingStrategy)) * time.Second), priced.GetExplicitDurationInS() }, func(started time.Time, duration float64) *time.Time { s := started.Add(time.Duration(duration)) diff --git a/models/workflow_execution/workflow_execution.go b/models/workflow_execution/workflow_execution.go index ec4c4c4..2cf65e9 100755 --- a/models/workflow_execution/workflow_execution.go +++ b/models/workflow_execution/workflow_execution.go @@ -8,6 +8,7 @@ import ( "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/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" "github.com/google/uuid" @@ -21,6 +22,7 @@ import ( */ type WorkflowExecution struct { 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 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 @@ -110,10 +112,51 @@ func (d *WorkflowExecution) VerifyAuth(request *tools.APIRequest) bool { } /* - booking is an activity reserved for not a long time investment. - ... purchase is dependant of a one time buying. - use of a datacenter or storage can't be buy for permanent. +booking is an activity reserved for not a long time investment. +... purchase is dependant of a one time buying. +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 { 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 { items := []*booking.Booking{} for itemID, priced := range priceds { + if !priced.IsBooked() { // book only that must be booked + continue + } if d.PeerBookByGraph == nil { d.PeerBookByGraph = map[string]map[string][]string{} } diff --git a/models/workflow_execution/workflow_scheduler.go b/models/workflow_execution/workflow_scheduler.go index 6156176..b0728bc 100755 --- a/models/workflow_execution/workflow_scheduler.go +++ b/models/workflow_execution/workflow_scheduler.go @@ -11,6 +11,7 @@ import ( "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" "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 Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task - SelectedBuyingStrategy pricing.BuyingStrategy `json:"selected_buying_strategy"` - SelectedPricingStrategy int `json:"selected_pricing_strategy"` + SelectedBuyingStrategy pricing.BuyingStrategy `json:"selected_buying_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 { @@ -79,8 +81,10 @@ func (ws *WorkflowSchedule) CheckBooking(wfID string, request *tools.APIRequest) if err != nil { return false, wf, []*WorkflowExecution{}, []*booking.Booking{}, err } + purchased := []*purchase_resource.PurchaseResource{} bookings := []*booking.Booking{} for _, exec := range execs { + purchased = append(purchased, exec.Buy(ws.SelectedBillingStrategy, ws.UUID, wfID, priceds)...) bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...) } diff --git a/tools/enums.go b/tools/enums.go index 2bb54e0..c0f1d42 100644 --- a/tools/enums.go +++ b/tools/enums.go @@ -27,6 +27,7 @@ const ( ADMIRALTY_KUBECONFIG ADMIRALTY_NODES COMPUTE_UNITS + BILL ) var NOAPI = "" @@ -67,6 +68,7 @@ var DefaultAPI = [...]string{ ADMIRALTY_KUBECONFIGAPI, ADMIRALTY_NODESAPI, DATACENTERAPI, + NOAPI, } // Bind the standard data name to the data type @@ -94,6 +96,7 @@ var Str = [...]string{ "admiralty_kubeconfig", "admiralty_node", "compute_units", + "bill", } func FromInt(i int) string { @@ -114,5 +117,7 @@ func (d DataType) EnumIndex() int { } 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} }