From 29b192211dc560c0c080f6bdde96cc1d03fd95ae Mon Sep 17 00:00:00 2001 From: mr Date: Fri, 20 Jun 2025 12:10:36 +0200 Subject: [PATCH] add sets up --- models/bill/bill.go | 15 ++++- models/billing_process.md | 2 + models/common/pricing/interfaces.go | 1 + models/common/pricing/pricing_profile.go | 5 +- models/order/order.go | 5 +- models/resources/compute.go | 14 ++--- models/resources/data.go | 14 ++--- models/resources/interfaces.go | 10 ++-- models/resources/priced_resource.go | 30 +++++----- models/resources/processing.go | 4 ++ models/resources/resource.go | 60 +++++++++---------- models/resources/storage.go | 14 ++--- models/resources/tests/compute_test.go | 13 +--- models/resources/tests/data_test.go | 7 +-- .../resources/tests/priced_resource_test.go | 6 +- models/resources/tests/resource_test.go | 4 +- models/resources/tests/storage_test.go | 4 +- models/resources/tests/workflow_test.go | 2 +- models/resources/workflow.go | 5 +- models/workflow/graph/graph.go | 12 ++-- models/workflow/workflow.go | 26 ++++---- .../workflow_execution/workflow_scheduler.go | 14 ++--- 22 files changed, 136 insertions(+), 131 deletions(-) create mode 100644 models/billing_process.md diff --git a/models/bill/bill.go b/models/bill/bill.go index 0b56d11..5df1751 100644 --- a/models/bill/bill.go +++ b/models/bill/bill.go @@ -26,7 +26,20 @@ type Bill struct { Total float64 `json:"total" bson:"total" validate:"required"` } -func DraftBill(order *order.Order, request *tools.APIRequest) (*Bill, error) { +func GenerateBill(order *order.Order, request *tools.APIRequest) (*Bill, error) { + // hhmmm : should get... the loop. + return &Bill{ + AbstractObject: utils.AbstractObject{ + Name: "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"), + IsDraft: false, + }, + OrderID: order.UUID, + Status: enum.PENDING, + // SubOrders: peerOrders, + }, nil +} + +func DraftFirstBill(order *order.Order, request *tools.APIRequest) (*Bill, error) { peers := map[string][]*PeerItemOrder{} for _, p := range order.Purchases { // TODO : if once diff --git a/models/billing_process.md b/models/billing_process.md new file mode 100644 index 0000000..fa20403 --- /dev/null +++ b/models/billing_process.md @@ -0,0 +1,2 @@ +# Billing process +Scheduler process a drafted order + a first bill corresponding to every once buying. \ No newline at end of file diff --git a/models/common/pricing/interfaces.go b/models/common/pricing/interfaces.go index fdbc62d..da8501f 100755 --- a/models/common/pricing/interfaces.go +++ b/models/common/pricing/interfaces.go @@ -12,6 +12,7 @@ type PricedItemITF interface { IsPurchasable() bool IsBooked() bool GetCreatorID() string + SelectPricing() PricingProfileITF GetLocationStart() *time.Time SetLocationStart(start time.Time) SetLocationEnd(end time.Time) diff --git a/models/common/pricing/pricing_profile.go b/models/common/pricing/pricing_profile.go index 79aacb5..92cbb3c 100755 --- a/models/common/pricing/pricing_profile.go +++ b/models/common/pricing/pricing_profile.go @@ -5,10 +5,11 @@ import ( ) type PricingProfileITF interface { - GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) - IsPurchasable() bool IsBooked() bool + IsPurchasable() bool + GetPurchase() BuyingStrategy GetOverrideStrategyValue() int + GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) } type RefundType int diff --git a/models/order/order.go b/models/order/order.go index 574c91d..a7819b0 100644 --- a/models/order/order.go +++ b/models/order/order.go @@ -5,6 +5,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" @@ -17,9 +18,11 @@ import ( type Order struct { utils.AbstractObject ExecutionsID string `json:"executions_id" bson:"executions_id" validate:"required"` + Status enum.CompletionStatus `json:"status" bson:"status" default:"0"` Purchases []*purchase_resource.PurchaseResource `json:"purchases" bson:"purchases"` Bookings []*booking.Booking `json:"bookings" bson:"bookings"` - Status enum.CompletionStatus `json:"status" bson:"status" default:"0"` + + Billing map[pricing.BillingStrategy][]*booking.Booking `json:"billing" bson:"billing"` } func (r *Order) StoreDraftDefault() { diff --git a/models/resources/compute.go b/models/resources/compute.go index fb3ab95..7a184cc 100755 --- a/models/resources/compute.go +++ b/models/resources/compute.go @@ -30,12 +30,11 @@ func (r *ComputeResource) GetType() string { return tools.COMPUTE_RESOURCE.String() } -func (abs *ComputeResource) ConvertToPricedResource( - t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF { +func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { if t != tools.COMPUTE_RESOURCE { return nil } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) + p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedComputeResource{ PricedResource: *priced, @@ -84,6 +83,10 @@ func (p *ComputeResourcePricingProfile) IsPurchasable() bool { return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION } +func (p *ComputeResourcePricingProfile) GetPurchase() pricing.BuyingStrategy { + return p.Pricing.BuyingStrategy +} + func (p *ComputeResourcePricingProfile) IsBooked() bool { if p.Pricing.BuyingStrategy == pricing.PERMANENT { p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION @@ -159,10 +162,7 @@ func (r *PricedComputeResource) GetPrice() (float64, error) { r.UsageEnd = &add } if r.SelectedPricing == nil { - if len(r.PricingProfiles) == 0 { - return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID) - } - r.SelectedPricing = r.PricingProfiles[0] + return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID) } pricing := r.SelectedPricing price := float64(0) diff --git a/models/resources/data.go b/models/resources/data.go index b0c47ac..aecf190 100755 --- a/models/resources/data.go +++ b/models/resources/data.go @@ -37,12 +37,11 @@ func (r *DataResource) GetType() string { return tools.DATA_RESOURCE.String() } -func (abs *DataResource) ConvertToPricedResource( - t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF { +func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { if t != tools.DATA_RESOURCE { return nil } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) + p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedDataResource{ PricedResource: *priced, @@ -137,6 +136,10 @@ func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDura return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) } +func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy { + return p.Pricing.BuyingStrategy +} + func (p *DataResourcePricingProfile) IsPurchasable() bool { return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION } @@ -166,10 +169,7 @@ func (r *PricedDataResource) GetPrice() (float64, error) { r.UsageEnd = &add } if r.SelectedPricing == nil { - if len(r.PricingProfiles) == 0 { - return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID) - } - r.SelectedPricing = r.PricingProfiles[0] + return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID) } pricing := r.SelectedPricing var err error diff --git a/models/resources/interfaces.go b/models/resources/interfaces.go index aff8cee..1a2a20d 100755 --- a/models/resources/interfaces.go +++ b/models/resources/interfaces.go @@ -9,9 +9,9 @@ import ( type ResourceInterface interface { utils.DBObject Trim() - ConvertToPricedResource(t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF + ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF GetType() string - GetSelectedInstance() utils.DBObject + GetSelectedInstance() ResourceInstanceITF ClearEnv() utils.DBObject SetAllowedInstances(request *tools.APIRequest) } @@ -22,15 +22,15 @@ type ResourceInstanceITF interface { GetName() string StoreDraftDefault() ClearEnv() - GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF + GetProfile() pricing.PricingProfileITF + GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) ClearPeerGroups() - GetSelectedPartnership() ResourcePartnerITF GetPartnerships(peerID string, groups []string) []ResourcePartnerITF } type ResourcePartnerITF interface { - GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF + GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPeerGroups() map[string][]string ClearPeerGroups() } diff --git a/models/resources/priced_resource.go b/models/resources/priced_resource.go index c65f697..161be32 100755 --- a/models/resources/priced_resource.go +++ b/models/resources/priced_resource.go @@ -10,17 +10,20 @@ import ( ) 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"` + 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"` + 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) SelectPricing() pricing.PricingProfileITF { + return abs.SelectedPricing } func (abs *PricedResource) GetID() string { @@ -90,10 +93,7 @@ func (r *PricedResource) GetPrice() (float64, error) { r.UsageEnd = &add } if r.SelectedPricing == nil { - if len(r.PricingProfiles) == 0 { - return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID) - } - r.SelectedPricing = r.PricingProfiles[0] + return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID) } pricing := r.SelectedPricing return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd) diff --git a/models/resources/processing.go b/models/resources/processing.go index 4661689..e6b6ea5 100755 --- a/models/resources/processing.go +++ b/models/resources/processing.go @@ -85,6 +85,10 @@ func (p *ProcessingResourcePricingProfile) IsBooked() bool { return p.Pricing.BuyingStrategy != pricing.PERMANENT } +func (p *ProcessingResourcePricingProfile) GetPurchase() pricing.BuyingStrategy { + return p.Pricing.BuyingStrategy +} + 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) } diff --git a/models/resources/resource.go b/models/resources/resource.go index 45a5a62..7f587ef 100755 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -24,7 +24,7 @@ type AbstractResource struct { SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance } -func (r *AbstractResource) GetSelectedInstance() utils.DBObject { +func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF { return nil } @@ -52,13 +52,19 @@ 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) 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, buyingStrategy, pricingStrategy) + profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups) + } + var profile pricing.PricingProfileITF + if t := abs.GetSelectedInstance(); t != nil { + profile = t.GetProfile() + } + if profile == nil && len(profiles) > 0 { + profile = profiles[0] } return &PricedResource{ Name: abs.Name, @@ -66,7 +72,7 @@ func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource( ResourceID: abs.UUID, ResourceType: t, InstancesRefs: instances, - PricingProfiles: profiles, + SelectedPricing: profile, CreatorID: abs.CreatorID, } } @@ -78,7 +84,7 @@ func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject { return abs } -func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() utils.DBObject { +func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF { if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex { return r.Instances[*r.SelectedInstanceIndex] } @@ -144,24 +150,14 @@ type Credentials struct { type ResourceInstance[T ResourcePartnerITF] struct { utils.AbstractObject - Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"` - Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"` - 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"` - SelectedPartnershipIndex *int `json:"selected_partnership_index,omitempty" bson:"selected_partnership_index,omitempty"` // SelectedInstance is the selected instance - Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` -} - -func (r *ResourceInstance[T]) GetSelectedPartnership() ResourcePartnerITF { - if r.SelectedPartnershipIndex != nil && len(r.Partnerships) > *r.SelectedPartnershipIndex { - return r.Partnerships[*r.SelectedPartnershipIndex] - } - if len(r.Partnerships) > 0 { - return r.Partnerships[0] - } - return nil + Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"` + Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"` + 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"` + SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"` + Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` } func (ri *ResourceInstance[T]) ClearEnv() { @@ -170,6 +166,10 @@ func (ri *ResourceInstance[T]) ClearEnv() { ri.Outputs = []models.Param{} } +func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF { + return ri.SelectedPricing +} + func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF { partners := []ResourcePartnerITF{} for _, p := range ri.Partnerships { @@ -184,10 +184,10 @@ func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) [ return partners } -func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF { +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, buyingStrategy, pricingStrategy)...) + pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...) } return pricings } @@ -222,16 +222,16 @@ note : il faut rajouté - une notion de facturation Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat. Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc. */ -func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF { +func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF { profiles := []pricing.PricingProfileITF{} if ri.PeerGroups[peerID] == nil { return profiles } for _, p := range ri.PeerGroups[peerID] { if slices.Contains(groups, p) || slices.Contains(groups, "*") { - for buyingS, ri := range ri.PricingProfiles { - if _, ok := ri[pricingStrategy]; ok && buyingS == buyingStrategy { - profiles = append(profiles, ri[pricingStrategy]) + for _, ri := range ri.PricingProfiles { + for _, i := range ri { + profiles = append(profiles, i) } } return profiles diff --git a/models/resources/storage.go b/models/resources/storage.go index f847182..a96f7b5 100755 --- a/models/resources/storage.go +++ b/models/resources/storage.go @@ -30,12 +30,11 @@ func (r *StorageResource) GetType() string { return tools.STORAGE_RESOURCE.String() } -func (abs *StorageResource) ConvertToPricedResource( - t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF { +func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { if t != tools.STORAGE_RESOURCE { return nil } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) + p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedStorageResource{ PricedResource: *priced, @@ -153,6 +152,10 @@ 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) GetPurchase() pricing.BuyingStrategy { + return p.Pricing.BuyingStrategy +} + func (p *StorageResourcePricingProfile) IsPurchasable() bool { return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION } @@ -188,10 +191,7 @@ func (r *PricedStorageResource) GetPrice() (float64, error) { r.UsageEnd = &add } if r.SelectedPricing == nil { - if len(r.PricingProfiles) == 0 { - return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID) - } - r.SelectedPricing = r.PricingProfiles[0] + return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID) } pricing := r.SelectedPricing var err error diff --git a/models/resources/tests/compute_test.go b/models/resources/tests/compute_test.go index 83cd5e8..00c764f 100644 --- a/models/resources/tests/compute_test.go +++ b/models/resources/tests/compute_test.go @@ -30,7 +30,7 @@ func TestComputeResource_ConvertToPricedResource(t *testing.T) { cr := &resources.ComputeResource{} cr.UUID = "comp123" cr.AbstractInstanciatedResource.UUID = cr.UUID - result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req, 0, 0) + result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req) assert.NotNil(t, result) assert.IsType(t, &resources.PricedComputeResource{}, result) } @@ -62,20 +62,9 @@ func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) { func TestPricedComputeResource_GetPrice(t *testing.T) { start := time.Now() end := start.Add(1 * time.Hour) - profile := &resources.ComputeResourcePricingProfile{ - CPUsPrices: map[string]float64{"Xeon": 1.0}, - GPUsPrices: map[string]float64{"Tesla": 2.0}, - RAMPrice: 0.5, - ExploitPricingProfile: pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{ - AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ - Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{Price: 1.0}, - }, - }, - } r := resources.PricedComputeResource{ PricedResource: resources.PricedResource{ ResourceID: "comp456", - PricingProfiles: []pricing.PricingProfileITF{profile}, UsageStart: &start, UsageEnd: &end, ExplicitBookingDurationS: 3600, diff --git a/models/resources/tests/data_test.go b/models/resources/tests/data_test.go index af7cc2e..b8a3380 100644 --- a/models/resources/tests/data_test.go +++ b/models/resources/tests/data_test.go @@ -27,10 +27,10 @@ func TestDataResource_GetAccessor(t *testing.T) { func TestDataResource_ConvertToPricedResource(t *testing.T) { d := &resources.DataResource{} d.UUID = "123" - res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{}, 0, 0) + res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{}) assert.IsType(t, &resources.PricedDataResource{}, res) - nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{}, 0, 0) + nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{}) assert.Nil(t, nilRes) } @@ -94,9 +94,6 @@ func TestPricedDataResource_GetPrice(t *testing.T) { PricedResource: resources.PricedResource{ UsageStart: &now, UsageEnd: &later, - PricingProfiles: []pricing.PricingProfileITF{ - pricingProfile, - }, }, } diff --git a/models/resources/tests/priced_resource_test.go b/models/resources/tests/priced_resource_test.go index d47d26e..28f3f4c 100644 --- a/models/resources/tests/priced_resource_test.go +++ b/models/resources/tests/priced_resource_test.go @@ -101,11 +101,9 @@ func TestGetPrice(t *testing.T) { t.Run("uses first profile if selected is nil", func(t *testing.T) { start := time.Now() end := start.Add(30 * time.Minute) - mock := &MockPricingProfile{ReturnCost: 42.0} r := &resources.PricedResource{ - PricingProfiles: []pricing.PricingProfileITF{mock}, - UsageStart: &start, - UsageEnd: &end, + UsageStart: &start, + UsageEnd: &end, } price, err := r.GetPrice() require.NoError(t, err) diff --git a/models/resources/tests/resource_test.go b/models/resources/tests/resource_test.go index a3fe979..6f4fbe9 100644 --- a/models/resources/tests/resource_test.go +++ b/models/resources/tests/resource_test.go @@ -20,7 +20,7 @@ func (m *MockInstance) GetID() string { return m.ID } func (m *MockInstance) GetName() string { return m.Name } func (m *MockInstance) ClearEnv() {} func (m *MockInstance) ClearPeerGroups() {} -func (m *MockInstance) GetPricingsProfiles(string, []string, int, int) []pricing.PricingProfileITF { +func (m *MockInstance) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF { return nil } func (m *MockInstance) GetPeerGroups() ([]resources.ResourcePartnerITF, []map[string][]string) { @@ -37,7 +37,7 @@ func (m *MockPartner) GetPeerGroups() map[string][]string { return m.groups } func (m *MockPartner) ClearPeerGroups() {} -func (m *MockPartner) GetPricingsProfiles(string, []string, int, int) []pricing.PricingProfileITF { +func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF { return nil } diff --git a/models/resources/tests/storage_test.go b/models/resources/tests/storage_test.go index 2e1d0e2..9b5c90c 100644 --- a/models/resources/tests/storage_test.go +++ b/models/resources/tests/storage_test.go @@ -26,14 +26,14 @@ func TestStorageResource_ConvertToPricedResource_ValidType(t *testing.T) { res := &resources.StorageResource{} res.AbstractInstanciatedResource.CreatorID = "creator" res.AbstractInstanciatedResource.UUID = "res-id" - priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{}, 0, 0) + priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{}) assert.NotNil(t, priced) assert.IsType(t, &resources.PricedStorageResource{}, priced) } func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) { res := &resources.StorageResource{} - priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{}, 0, 0) + priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{}) assert.Nil(t, priced) } diff --git a/models/resources/tests/workflow_test.go b/models/resources/tests/workflow_test.go index 5c699ec..5d8eeb9 100644 --- a/models/resources/tests/workflow_test.go +++ b/models/resources/tests/workflow_test.go @@ -32,7 +32,7 @@ func TestWorkflowResource_ConvertToPricedResource(t *testing.T) { Groups: []string{"group1"}, } - pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req, 0, 0) + pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req) assert.Equal(t, "creator-id", pr.GetCreatorID()) assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType()) } diff --git a/models/resources/workflow.go b/models/resources/workflow.go index 9948dd4..edba6f0 100755 --- a/models/resources/workflow.go +++ b/models/resources/workflow.go @@ -16,7 +16,7 @@ type WorkflowResource struct { } func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor { - return NewAccessor[*ComputeResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) + return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) } func (r *WorkflowResource) GetType() string { @@ -34,8 +34,7 @@ func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) { /* EMPTY */ } -func (w *WorkflowResource) ConvertToPricedResource( - t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF { +func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { return &PricedResource{ Name: w.Name, Logo: w.Logo, diff --git a/models/workflow/graph/graph.go b/models/workflow/graph/graph.go index c34f666..991d321 100644 --- a/models/workflow/graph/graph.go +++ b/models/workflow/graph/graph.go @@ -55,7 +55,7 @@ func (wf *Graph) IsWorkflow(item GraphItem) bool { } func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface, - f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) (float64, float64) { + f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) { nearestStart := float64(10000000000) oneIsInfinite := false longestDuration := float64(0) @@ -67,7 +67,7 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce } 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, buyingStrategy, pricingStrategy) + priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request) if source != "" { if priced.GetLocationStart() != nil { near := float64(priced.GetLocationStart().Sub(start).Seconds()) @@ -96,7 +96,7 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce /* * GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing */ -func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) float64 { +func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 { currents := []float64{} // list of current time for _, link := range g.Links { // for each link var source string // source is the source of the link @@ -112,13 +112,13 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI if r == nil { // if item is nil, continue continue } - priced := r.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) + 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, buyingStrategy, pricingStrategy) // get the average time before start of the source - currents = append(currents, current) // append the current to the currents + 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 { diff --git a/models/workflow/workflow.go b/models/workflow/workflow.go index d140442..3cfdf2b 100644 --- a/models/workflow/workflow.go +++ b/models/workflow/workflow.go @@ -83,7 +83,7 @@ func (w *Workflow) GetPricedItem( for _, item := range w.Graph.Items { if f(item) { dt, res := item.GetResource() - ord := res.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) + ord := res.ConvertToPricedResource(dt, request) list_datas[res.GetID()] = ord } } @@ -166,12 +166,11 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) { return true, nil } -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) (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, + 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, buyingStrategy, pricingStrategy)) * time.Second), priced.GetExplicitDurationInS() + 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 @@ -179,7 +178,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR if err != nil { return 0, priceds, nil, err } - if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, buyingStrategy, pricingStrategy, + 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 { @@ -188,14 +187,14 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR 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, buyingStrategy, pricingStrategy, + 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, buyingStrategy, pricingStrategy) + }, request) return start.Add(time.Duration(nearestStart) * time.Second), longestDuration }, func(started time.Time, duration float64) *time.Time { s := started.Add(time.Duration(duration)) @@ -205,8 +204,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR } } longest := common.GetPlannerLongestTime(end, priceds, request) - if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, - buyingStrategy, pricingStrategy, wf.Graph.IsWorkflow, + 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) @@ -214,7 +212,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR if code != 200 || err != nil { return start, longest } - if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request, buyingStrategy, pricingStrategy); err != nil { + if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil { return start, longest } else if neoLongest > longest { longest = neoLongest @@ -231,7 +229,6 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR func plan[T resources.ResourceInterface]( dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest, - buyingStrategy int, pricingStrategy int, 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) { resources := []T{} for _, item := range wf.GetGraphItems(f) { @@ -242,7 +239,10 @@ func plan[T resources.ResourceInterface]( if realItem == nil { return resources, priceds, errors.New("could not load the processing resource") } - priced := realItem.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) + priced := realItem.ConvertToPricedResource(dt, request) + if priced.SelectPricing() == nil { + return resources, priceds, errors.New("no pricings are selected... can't proceed") + } started, duration := start(realItem, priced) priced.SetLocationStart(started) if duration >= 0 { diff --git a/models/workflow_execution/workflow_scheduler.go b/models/workflow_execution/workflow_scheduler.go index 3590047..1ee63a6 100755 --- a/models/workflow_execution/workflow_scheduler.go +++ b/models/workflow_execution/workflow_scheduler.go @@ -37,8 +37,6 @@ 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"` SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"` } @@ -70,7 +68,7 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, 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, int(ws.SelectedBuyingStrategy), ws.SelectedPricingStrategy) + longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request) if err != nil { return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err } @@ -90,10 +88,6 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...) } - if err := ws.GenerateOrder(purchased, bookings, request); err != nil { - return false, wf, execs, purchased, bookings, err - } - errCh := make(chan error, len(bookings)) var m sync.Mutex @@ -107,6 +101,10 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest } } + if err := ws.GenerateOrder(purchased, bookings, request); err != nil { + return false, wf, execs, purchased, bookings, err + } + return true, wf, execs, purchased, bookings, nil } @@ -122,7 +120,7 @@ func (ws *WorkflowSchedule) GenerateOrder(purchases []*purchase_resource.Purchas Status: enum.PENDING, } if res, _, err := order.NewAccessor(request).StoreOne(newOrder); err == nil { - if _, err := bill.DraftBill(res.(*order.Order), request); err != nil { + if _, err := bill.DraftFirstBill(res.(*order.Order), request); err != nil { return err } return nil