diff --git a/entrypoint.go b/entrypoint.go index 25db0cf..e9f3899 100644 --- a/entrypoint.go +++ b/entrypoint.go @@ -57,6 +57,7 @@ const ( LIVE_DATACENTER = tools.LIVE_DATACENTER LIVE_STORAGE = tools.LIVE_STORAGE PURCHASE_RESOURCE = tools.PURCHASE_RESOURCE + NATIVE_TOOL = tools.NATIVE_TOOL ) // will turn into standards api hostnames @@ -108,7 +109,7 @@ func InitDaemon(appName string) { logs.SetLogger(logs.CreateLogger("main")) // Load the right config file o := GetConfLoader() - + resources.InitNative() // feed the library with the loaded config SetConfig( o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"), @@ -265,8 +266,8 @@ func (r *Request) Schedule(wfID string, scheduler *workflow_execution.WorkflowSc return ws, nil } -func (r *Request) CheckBooking(wfID string, start string, end string, durationInS float64, cron string) bool { - ok, _, _, _, _, err := workflow_execution.NewScheduler(start, end, durationInS, cron).GetBuyAndBook(wfID, &tools.APIRequest{ +func (r *Request) CheckBooking(wfID string, mode int, start string, end string, durationInS float64, cron string) bool { + ok, _, _, _, _, err := workflow_execution.NewScheduler(mode, start, end, durationInS, cron).GetBuyAndBook(wfID, &tools.APIRequest{ Caller: r.caller, Username: r.user, PeerID: r.peerID, @@ -598,7 +599,6 @@ func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource { return nil } - // ============== ADMIRALTY ============== // Returns a concatenation of the peerId and namespace in order for // kubernetes ressources to have a unique name, under 63 characters @@ -614,48 +614,48 @@ func GetConcatenatedName(peerId string, namespace string) string { func LoadOneStorage(storageId string, user string, peerID string, groups []string) (*resources.StorageResource, error) { - res := NewRequest(LibDataEnum(STORAGE_RESOURCE), user, peerID, groups,nil).LoadOne(storageId) + res := NewRequest(LibDataEnum(STORAGE_RESOURCE), user, peerID, groups, nil).LoadOne(storageId) if res.Code != 200 { l := GetLogger() l.Error().Msg("Error while loading storage ressource " + storageId) - return nil,fmt.Errorf(res.Err) + return nil, fmt.Errorf(res.Err) } - return res.ToStorageResource(), nil + return res.ToStorageResource(), nil } func LoadOneComputing(computingId string, user string, peerID string, groups []string) (*resources.ComputeResource, error) { - res := NewRequest(LibDataEnum(COMPUTE_RESOURCE), user, peerID, groups,nil).LoadOne(computingId) + res := NewRequest(LibDataEnum(COMPUTE_RESOURCE), user, peerID, groups, nil).LoadOne(computingId) if res.Code != 200 { l := GetLogger() l.Error().Msg("Error while loading computing ressource " + computingId) - return nil,fmt.Errorf(res.Err) + return nil, fmt.Errorf(res.Err) } - return res.ToComputeResource(), nil -} + return res.ToComputeResource(), nil +} func LoadOneProcessing(processingId string, user string, peerID string, groups []string) (*resources.ProcessingResource, error) { - res := NewRequest(LibDataEnum(PROCESSING_RESOURCE), user, peerID, groups,nil).LoadOne(processingId) + res := NewRequest(LibDataEnum(PROCESSING_RESOURCE), user, peerID, groups, nil).LoadOne(processingId) if res.Code != 200 { l := GetLogger() l.Error().Msg("Error while loading processing ressource " + processingId) - return nil,fmt.Errorf(res.Err) + return nil, fmt.Errorf(res.Err) } - return res.ToProcessingResource(), nil + return res.ToProcessingResource(), nil } func LoadOneData(dataId string, user string, peerID string, groups []string) (*resources.DataResource, error) { - res := NewRequest(LibDataEnum(DATA_RESOURCE), user, peerID, groups,nil).LoadOne(dataId) + res := NewRequest(LibDataEnum(DATA_RESOURCE), user, peerID, groups, nil).LoadOne(dataId) if res.Code != 200 { l := GetLogger() l.Error().Msg("Error while loading data ressource " + dataId) - return nil,fmt.Errorf(res.Err) + return nil, fmt.Errorf(res.Err) } return res.ToDataResource(), nil - -} \ No newline at end of file + +} diff --git a/models/bill/bill.go b/models/bill/bill.go index 32bd67e..9cea4d2 100644 --- a/models/bill/bill.go +++ b/models/bill/bill.go @@ -169,7 +169,7 @@ func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error { for _, b := range d.Items { - tot, err := b.GetPrice(request) // missing something + tot, err := b.GetPriceHT(request) // missing something if err != nil { return err } @@ -184,7 +184,7 @@ type PeerItemOrder struct { Item map[string]interface{} `json:"item,omitempty" bson:"item,omitempty"` } -func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) { +func (d *PeerItemOrder) GetPriceHT(request *tools.APIRequest) (float64, error) { /////////// Temporary in order to allow GenerateOrder to complete while billing is still WIP if d.Purchase == nil { return 0, nil @@ -210,7 +210,7 @@ func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) { } } } - p, err := priced.GetPrice() + p, err := priced.GetPriceHT() if err != nil { return 0, err } diff --git a/models/booking/booking.go b/models/booking/booking.go index dc90509..64622e1 100644 --- a/models/booking/booking.go +++ b/models/booking/booking.go @@ -22,7 +22,7 @@ type Booking struct { ExecutionMetrics map[string][]models.MetricsSnapshot `json:"metrics,omitempty" bson:"metrics,omitempty"` ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions - DestPeerID string `json:"dest_peer_id,omitempty" bson:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer + DestPeerID string `json:"dest_peer_id,omitempty" bson:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer WorkflowID string `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"` State enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"` // State is the state of the booking @@ -118,7 +118,7 @@ func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor { return NewAccessor(request) // Create a new instance of the accessor } -func (d *Booking) VerifyAuth(request *tools.APIRequest) bool { +func (d *Booking) VerifyAuth(callName string, request *tools.APIRequest) bool { return true } diff --git a/models/booking/enums.go b/models/booking/enums.go new file mode 100644 index 0000000..5ac012a --- /dev/null +++ b/models/booking/enums.go @@ -0,0 +1,23 @@ +package booking + +type BookingMode int + +const ( + PLANNED BookingMode = iota // predictible + PREEMPTED // can be both predictible or unpredictible, first one asking for a quick exec, second on event, but we pay to preempt in any case. + WHEN_POSSIBLE // unpredictable, two mode of payment can be available on that case: fixed, or per USE +) + +/* +Ok make a point there: +There is 3 notions about booking & payment : + Booking mode : WHEN is executed + Buying mode : Duration of payment + Pricing Mode : How Many time we pay + + +We can simplify Buying Mode and Pricing Mode, some Buying Mode implied limited pricing mode +Such as Rules. Just like PERMANENT BUYING can be paid only once. + +Booking Mode on WHEN POSSIBLE make an exception, because we can't know when executed. +*/ diff --git a/models/booking/tests/booking_test.go b/models/booking/tests/booking_test.go index f8ead5f..62c23bc 100644 --- a/models/booking/tests/booking_test.go +++ b/models/booking/tests/booking_test.go @@ -42,7 +42,7 @@ func TestBooking_GetAccessor(t *testing.T) { } func TestBooking_VerifyAuth(t *testing.T) { - assert.True(t, (&booking.Booking{}).VerifyAuth(nil)) + assert.True(t, (&booking.Booking{}).VerifyAuth("get", nil)) } func TestBooking_StoreDraftDefault(t *testing.T) { diff --git a/models/collaborative_area/collaborative_area.go b/models/collaborative_area/collaborative_area.go index f4d2481..3228fb4 100644 --- a/models/collaborative_area/collaborative_area.go +++ b/models/collaborative_area/collaborative_area.go @@ -71,7 +71,7 @@ func (ao *CollaborativeArea) Clear(peerID string) { ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC() } -func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool { +func (ao *CollaborativeArea) VerifyAuth(callName string, request *tools.APIRequest) bool { if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil { if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist { if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) { @@ -84,7 +84,7 @@ func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool { } } } - return ao.AbstractObject.VerifyAuth(request) + return ao.AbstractObject.VerifyAuth(callName, request) } func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor { @@ -97,7 +97,7 @@ func (d *CollaborativeArea) Trim() *CollaborativeArea { func (d *CollaborativeArea) StoreDraftDefault() { d.AllowedPeersGroup = map[string][]string{ - d.CreatorID: []string{"*"}, + d.CreatorID: {"*"}, } d.IsDraft = false } diff --git a/models/collaborative_area/rules/rule/rule.go b/models/collaborative_area/rules/rule/rule.go index 6eff492..d2c6830 100644 --- a/models/collaborative_area/rules/rule/rule.go +++ b/models/collaborative_area/rules/rule/rule.go @@ -24,6 +24,6 @@ func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor { return NewAccessor(request) } -func (d *Rule) VerifyAuth(request *tools.APIRequest) bool { +func (d *Rule) VerifyAuth(callName string, request *tools.APIRequest) bool { return true } diff --git a/models/common/pricing/interfaces.go b/models/common/pricing/interfaces.go index da8501f..30c6f79 100755 --- a/models/common/pricing/interfaces.go +++ b/models/common/pricing/interfaces.go @@ -3,6 +3,7 @@ package pricing import ( "time" + "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/tools" ) @@ -11,6 +12,9 @@ type PricedItemITF interface { GetType() tools.DataType IsPurchasable() bool IsBooked() bool + GetQuantity() int + AddQuantity(amount int) + GetBookingMode() booking.BookingMode GetCreatorID() string SelectPricing() PricingProfileITF GetLocationStart() *time.Time @@ -18,5 +22,5 @@ type PricedItemITF interface { SetLocationEnd(end time.Time) GetLocationEnd() *time.Time GetExplicitDurationInS() float64 - GetPrice() (float64, error) + GetPriceHT() (float64, error) } diff --git a/models/common/pricing/pricing_profile.go b/models/common/pricing/pricing_profile.go index 92cbb3c..d5aa1db 100755 --- a/models/common/pricing/pricing_profile.go +++ b/models/common/pricing/pricing_profile.go @@ -9,7 +9,7 @@ type PricingProfileITF interface { IsPurchasable() bool GetPurchase() BuyingStrategy GetOverrideStrategyValue() int - GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) + GetPriceHT(quantity float64, val float64, start time.Time, end time.Time, variation []*PricingVariation, params ...string) (float64, error) } type RefundType int @@ -34,10 +34,37 @@ type AccessPricingProfile[T Strategy] struct { // only use for acces such as : D RefundRatio int32 `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing } +func (a AccessPricingProfile[T]) IsBooked() bool { + return a.Pricing.BuyingStrategy == SUBSCRIPTION +} + +func (a AccessPricingProfile[T]) IsPurchasable() bool { + return a.Pricing.BuyingStrategy == PERMANENT +} + +func (a AccessPricingProfile[T]) GetPurchase() BuyingStrategy { + return a.Pricing.BuyingStrategy +} + +func (a AccessPricingProfile[T]) GetPriceHT(quantity float64, val float64, start time.Time, end time.Time, variations []*PricingVariation, params ...string) (float64, error) { + return a.Pricing.GetPriceHT(quantity, val, start, &end, variations) +} + func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int { return -1 } +func GetDefaultPricingProfile() PricingProfileITF { + return &AccessPricingProfile[TimePricingStrategy]{ + Pricing: PricingStrategy[TimePricingStrategy]{ + Price: 0, + Currency: "EUR", + BuyingStrategy: PERMANENT, + TimePricingStrategy: ONCE, + }, + } +} + type ExploitPrivilegeStrategy int const ( diff --git a/models/common/pricing/pricing_strategy.go b/models/common/pricing/pricing_strategy.go index 728e794..fd78346 100755 --- a/models/common/pricing/pricing_strategy.go +++ b/models/common/pricing/pricing_strategy.go @@ -163,11 +163,37 @@ type PricingStrategy[T Strategy] struct { OverrideStrategy T `json:"override_strategy" bson:"override_strategy" default:"-1"` // Modulation is the modulation of the pricing } -func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) { +func (p PricingStrategy[T]) GetPriceHT(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time, variations []*PricingVariation) (float64, error) { if p.BuyingStrategy == SUBSCRIPTION { - return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end) - } else if p.BuyingStrategy == PERMANENT { + price, err := BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end) + if err != nil { + return 0, err + } + if variations != nil { + for _, v := range variations { + price = v.GetPriceHT(price) + } + return price, nil + } + return p.Price, nil + + } else if p.BuyingStrategy == PERMANENT { + if variations != nil { + price := p.Price + for _, v := range variations { + price = v.GetPriceHT(price) + } + return price, nil + } + return p.Price, nil + } + if variations != nil { + price := p.Price + for _, v := range variations { + price = v.GetPriceHT(price) + } + return price, nil } return p.Price * float64(amountOfData), nil } @@ -183,3 +209,18 @@ func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy { func (p PricingStrategy[T]) GetOverrideStrategy() T { return p.OverrideStrategy } + +type PricingVariation struct { + Inflate bool `json:"inflate" bson:"price"` // Price is the Price of the pricing + Percentage float64 `json:"percent" bson:"percent"` // Currency is the currency of the pricing // Modulation is the modulation of the pricing + Priority int `json:"priority" bson:"priority"` +} + +func (pv *PricingVariation) GetPriceHT(priceHT float64) float64 { + value := (priceHT * pv.Percentage) / 100 + if pv.Inflate { + return priceHT + value + } else { + return priceHT - value + } +} diff --git a/models/common/pricing/tests/pricing_test.go b/models/common/pricing/tests/pricing_test.go index 2aee55f..0aaa751 100644 --- a/models/common/pricing/tests/pricing_test.go +++ b/models/common/pricing/tests/pricing_test.go @@ -100,7 +100,7 @@ func TestPricingStrategy_Getters(t *testing.T) { assert.Equal(t, DummyStrategy(1), ps.GetOverrideStrategy()) } -func TestPricingStrategy_GetPrice(t *testing.T) { +func TestPricingStrategy_GetPriceHT(t *testing.T) { start := time.Now() end := start.Add(1 * time.Hour) @@ -111,19 +111,19 @@ func TestPricingStrategy_GetPrice(t *testing.T) { TimePricingStrategy: pricing.PER_HOUR, } - p, err := ps.GetPrice(2, 3600, start, &end) + p, err := ps.GetPriceHT(2, 3600, start, &end, nil) assert.NoError(t, err) assert.True(t, p > 0) // UNLIMITED case ps.BuyingStrategy = pricing.PERMANENT - p, err = ps.GetPrice(10, 0, start, &end) + p, err = ps.GetPriceHT(10, 0, start, &end, nil) assert.NoError(t, err) assert.Equal(t, 5.0, p) // PAY_PER_USE case //ps.BuyingStrategy = pricing.PAY_PER_USE - p, err = ps.GetPrice(3, 0, start, &end) + p, err = ps.GetPriceHT(3, 0, start, &end, nil) assert.NoError(t, err) assert.Equal(t, 15.0, p) } diff --git a/models/models.go b/models/models.go index 2131f35..dc40f2f 100644 --- a/models/models.go +++ b/models/models.go @@ -29,6 +29,7 @@ var ModelsCatalog = map[string]func() utils.DBObject{ tools.COMPUTE_RESOURCE.String(): func() utils.DBObject { return &resource.ComputeResource{} }, tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} }, tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} }, + tools.NATIVE_TOOL.String(): func() utils.DBObject { return &resource.NativeTool{} }, tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} }, tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} }, tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} }, diff --git a/models/peer/peer.go b/models/peer/peer.go index b7962e6..70ebd4c 100644 --- a/models/peer/peer.go +++ b/models/peer/peer.go @@ -26,6 +26,24 @@ func (m PeerState) EnumIndex() int { return int(m) } +func GetSelf() (utils.ShallowDBObject, string) { + d, code, err := NewAccessor(nil).Search(nil, SELF.String(), false) + if code != 200 || err != nil || len(d) == 0 { + return nil, "" + } + id := d[0].GetID() + return d[0], id +} + +func IsMySelf(peerID string) (bool, string) { + d, code, err := NewAccessor(nil).Search(nil, SELF.String(), false) + if code != 200 || err != nil || len(d) == 0 { + return false, "" + } + id := d[0].GetID() + return peerID == id, id +} + // Peer is a struct that represents a peer type Peer struct { utils.AbstractObject @@ -37,7 +55,7 @@ type Peer struct { FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried } -func (ao *Peer) VerifyAuth(request *tools.APIRequest) bool { +func (ao *Peer) VerifyAuth(callName string, request *tools.APIRequest) bool { return true } diff --git a/models/resources/compute.go b/models/resources/compute.go index 64bc5db..9db6593 100755 --- a/models/resources/compute.go +++ b/models/resources/compute.go @@ -31,15 +31,18 @@ func (r *ComputeResource) GetType() string { return tools.COMPUTE_RESOURCE.String() } -func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { +func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { if t != tools.COMPUTE_RESOURCE { - return nil + return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Compute") + } + p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request) + if err != nil { + return nil, err } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedComputeResource{ PricedResource: *priced, - } + }, nil } type ComputeNode struct { @@ -68,32 +71,6 @@ func NewComputeResourceInstance(name string, peerID string) ResourceInstanceITF UUID: uuid.New().String(), Name: name, }, - Partnerships: []*ComputeResourcePartnership{ - { - ResourcePartnerShip: ResourcePartnerShip[*ComputeResourcePricingProfile]{ - Namespace: "default", - PeerGroups: map[string][]string{ - peerID: {"*"}, - }, - PricingProfiles: map[int]map[int]*ComputeResourcePricingProfile{ - 0: { - 0: &ComputeResourcePricingProfile{ - ExploitPricingProfile: pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{ - AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ - Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{ - Price: 0, - Currency: "EUR", - TimePricingStrategy: pricing.ONCE, - BuyingStrategy: pricing.PERMANENT, - }, - }, - }, - }, - }, - }, - }, - }, - }, }, } } @@ -138,7 +115,7 @@ func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int { // NOT A PROPER QUANTITY // amountOfData is the number of CPUs, GPUs or RAM dependings on the params -func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) { +func (p *ComputeResourcePricingProfile) GetPriceHT(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, variation []*pricing.PricingVariation, params ...string) (float64, error) { if len(params) < 1 { return 0, errors.New("params must be set") } @@ -148,7 +125,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD if _, ok := p.CPUsPrices[model]; ok { p.Pricing.Price = p.CPUsPrices[model] } - r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) + r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation) if err != nil { return 0, err } @@ -159,7 +136,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD if _, ok := p.GPUsPrices[model]; ok { p.Pricing.Price = p.GPUsPrices[model] } - r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) + r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation) if err != nil { return 0, err } @@ -169,7 +146,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD if p.RAMPrice >= 0 { p.Pricing.Price = p.RAMPrice } - r, err := p.Pricing.GetPrice(float64(amountOfData), explicitDuration, start, &end) + r, err := p.Pricing.GetPriceHT(float64(amountOfData), explicitDuration, start, &end, variation) if err != nil { return 0, err } @@ -190,14 +167,17 @@ func (r *PricedComputeResource) GetType() tools.DataType { return tools.COMPUTE_RESOURCE } -func (r *PricedComputeResource) GetPrice() (float64, error) { - now := time.Now() - if r.UsageStart == nil { - r.UsageStart = &now +func (r *PricedComputeResource) GetPriceHT() (float64, error) { + if r.BookingConfiguration == nil { + r.BookingConfiguration = &BookingConfiguration{} } - if r.UsageEnd == nil { - add := r.UsageStart.Add(time.Duration(1 * time.Hour)) - r.UsageEnd = &add + now := time.Now() + if r.BookingConfiguration.UsageStart == nil { + r.BookingConfiguration.UsageStart = &now + } + if r.BookingConfiguration.UsageEnd == nil { + add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour)) + r.BookingConfiguration.UsageEnd = &add } if r.SelectedPricing == nil { return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID) @@ -206,14 +186,17 @@ func (r *PricedComputeResource) GetPrice() (float64, error) { price := float64(0) for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} { for model, amountOfData := range l { - cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "cpus", model) + cpus, err := pricing.GetPriceHT(float64(amountOfData), + r.BookingConfiguration.ExplicitBookingDurationS, *r.BookingConfiguration.UsageStart, + *r.BookingConfiguration.UsageEnd, r.Variations, "cpus", model) if err != nil { return 0, err } price += cpus } } - ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "ram") + ram, err := pricing.GetPriceHT(r.RAMLocated, r.BookingConfiguration.ExplicitBookingDurationS, + *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations, "ram") if err != nil { return 0, err } diff --git a/models/resources/data.go b/models/resources/data.go index a22afc2..0c735ba 100755 --- a/models/resources/data.go +++ b/models/resources/data.go @@ -38,15 +38,18 @@ func (r *DataResource) GetType() string { return tools.DATA_RESOURCE.String() } -func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { +func (abs *DataResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { if t != tools.DATA_RESOURCE { - return nil + return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Data") + } + p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request) + if err != nil { + return nil, err } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedDataResource{ PricedResource: *priced, - } + }, nil } type DataInstance struct { @@ -61,30 +64,6 @@ func NewDataInstance(name string, peerID string) ResourceInstanceITF { UUID: uuid.New().String(), Name: name, }, - Partnerships: []*DataResourcePartnership{ - { - ResourcePartnerShip: ResourcePartnerShip[*DataResourcePricingProfile]{ - Namespace: "default", - PeerGroups: map[string][]string{ - peerID: {"*"}, - }, - PricingProfiles: map[int]map[int]*DataResourcePricingProfile{ - 0: { - 0: &DataResourcePricingProfile{ - AccessPricingProfile: pricing.AccessPricingProfile[DataResourcePricingStrategy]{ - Pricing: pricing.PricingStrategy[DataResourcePricingStrategy]{ - Price: 0, - Currency: "EUR", - TimePricingStrategy: pricing.ONCE, - BuyingStrategy: pricing.PERMANENT, - }, - }, - }, - }, - }, - }, - }, - }, }, } } @@ -172,14 +151,6 @@ func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int { return p.Pricing.OverrideStrategy.GetStrategyValue() } -func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) { - return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) -} - -func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy { - return p.Pricing.BuyingStrategy -} - func (p *DataResourcePricingProfile) IsPurchasable() bool { return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION } @@ -198,15 +169,18 @@ func (r *PricedDataResource) GetType() tools.DataType { return tools.DATA_RESOURCE } -func (r *PricedDataResource) GetPrice() (float64, error) { - fmt.Println("GetPrice", r.UsageStart, r.UsageEnd) - now := time.Now() - if r.UsageStart == nil { - r.UsageStart = &now +func (r *PricedDataResource) GetPriceHT() (float64, error) { + if r.BookingConfiguration == nil { + r.BookingConfiguration = &BookingConfiguration{} } - if r.UsageEnd == nil { - add := r.UsageStart.Add(time.Duration(1 * time.Hour)) - r.UsageEnd = &add + fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd) + now := time.Now() + if r.BookingConfiguration.UsageStart == nil { + r.BookingConfiguration.UsageStart = &now + } + if r.BookingConfiguration.UsageEnd == nil { + add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour)) + r.BookingConfiguration.UsageEnd = &add } if r.SelectedPricing == nil { return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID) @@ -220,5 +194,7 @@ func (r *PricedDataResource) GetPrice() (float64, error) { return 0, err } } - return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd) + + return pricing.GetPriceHT(amountOfData, r.BookingConfiguration.ExplicitBookingDurationS, + *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations) } diff --git a/models/resources/interfaces.go b/models/resources/interfaces.go index 1209c69..48b9b61 100755 --- a/models/resources/interfaces.go +++ b/models/resources/interfaces.go @@ -1,6 +1,7 @@ package resources import ( + "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" @@ -9,9 +10,10 @@ import ( type ResourceInterface interface { utils.DBObject Trim() - ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF + GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation + ConvertToPricedResource(t tools.DataType, a *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, b *int, request *tools.APIRequest) (pricing.PricedItemITF, error) GetType() string - GetSelectedInstance() ResourceInstanceITF + GetSelectedInstance(selected *int) ResourceInstanceITF ClearEnv() utils.DBObject SetAllowedInstances(request *tools.APIRequest) AddInstances(instance ResourceInstanceITF) @@ -23,17 +25,15 @@ type ResourceInstanceITF interface { GetName() string StoreDraftDefault() ClearEnv() - GetProfile() pricing.PricingProfileITF + GetProfile(peerID string, partnershipIndex *int, buying *int, strategy *int) pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) ClearPeerGroups() - GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF - GetPartnerships(peerID string, groups []string) []ResourcePartnerITF } type ResourcePartnerITF interface { GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPeerGroups() map[string][]string ClearPeerGroups() - GetProfile(buying int, strategy int) pricing.PricingProfileITF + GetProfile(buying *int, strategy *int) pricing.PricingProfileITF } diff --git a/models/resources/models.go b/models/resources/models.go index 01eb610..5ff7ab7 100755 --- a/models/resources/models.go +++ b/models/resources/models.go @@ -11,12 +11,14 @@ type ResourceSet struct { Processings []string `bson:"processings,omitempty" json:"processings,omitempty"` Computes []string `bson:"computes,omitempty" json:"computes,omitempty"` Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"` + NativeTool []string `bson:"native,omitempty" json:"native,omitempty"` DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"` StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"` ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"` ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"` WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"` + NativeTools []*NativeTool `bson:"-" json:"native_tools,omitempty"` } func (r *ResourceSet) Clear() { @@ -62,4 +64,5 @@ type ItemResource struct { Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"` Compute *ComputeResource `bson:"compute,omitempty" json:"compute,omitempty"` Workflow *WorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"` + NativeTool *NativeTool `bson:"native_tools,omitempty" json:"native_tools,omitempty"` } diff --git a/models/resources/native_tools.go b/models/resources/native_tools.go new file mode 100644 index 0000000..98bfd49 --- /dev/null +++ b/models/resources/native_tools.go @@ -0,0 +1,73 @@ +package resources + +import ( + "encoding/json" + + "cloud.o-forge.io/core/oc-lib/models/common/pricing" + "cloud.o-forge.io/core/oc-lib/models/resources/native_tools" + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +/* +* NativeT ools is a struct that represents Native Functionnality of OPENCLOUD + */ +type NativeTool struct { + AbstractResource + Kind int `json:"kind" bson:"kind" validate:"required"` + Params map[string]interface{} +} + +func (d *NativeTool) SetName(name string) { + d.Name = name +} + +func (d *NativeTool) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*NativeTool](tools.NATIVE_TOOL, request, func() utils.DBObject { return &NativeTool{} }) +} + +func (r *NativeTool) AddInstances(instance ResourceInstanceITF) { +} + +func (r *NativeTool) GetType() string { + return tools.NATIVE_TOOL.String() +} + +func (d *NativeTool) ClearEnv() utils.DBObject { + return d +} + +func (d *NativeTool) Trim() { + /* EMPTY */ +} +func (w *NativeTool) SetAllowedInstances(request *tools.APIRequest) { + /* EMPTY */ +} + +func (w *NativeTool) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { + return &PricedResource{ + Name: w.Name, + Logo: w.Logo, + ResourceID: w.UUID, + ResourceType: t, + Quantity: 1, + CreatorID: w.CreatorID, + }, nil +} + +func InitNative() { + for _, kind := range []native_tools.NativeToolsEnum{native_tools.WORKFLOW_EVENT} { + newNative := &NativeTool{} + access := newNative.GetAccessor(&tools.APIRequest{Admin: true}) + l, _, err := access.Search(nil, kind.String(), false) + if err != nil || len(l) == 0 { + newNative.Name = kind.String() + newNative.Kind = int(kind) + b, _ := json.Marshal(kind.Params()) + var m map[string]interface{} + json.Unmarshal(b, &m) + newNative.Params = m + access.StoreOne(newNative) + } + } +} diff --git a/models/resources/native_tools/enums.go b/models/resources/native_tools/enums.go new file mode 100644 index 0000000..1a0721c --- /dev/null +++ b/models/resources/native_tools/enums.go @@ -0,0 +1,23 @@ +package native_tools + +type NativeToolsEnum int + +const ( + WORKFLOW_EVENT NativeToolsEnum = iota +) + +var Params = [...]interface{}{ + WorkflowEventParams{}, +} + +var Str = [...]string{ + "WORKFLOW_EVENT", +} + +func (d NativeToolsEnum) Params() interface{} { + return Str[d] +} + +func (d NativeToolsEnum) String() string { + return Str[d] +} diff --git a/models/resources/native_tools/workflow_event.go b/models/resources/native_tools/workflow_event.go new file mode 100644 index 0000000..4cf7b9b --- /dev/null +++ b/models/resources/native_tools/workflow_event.go @@ -0,0 +1,19 @@ +package native_tools + +import ( + "cloud.o-forge.io/core/oc-lib/models/booking" + "cloud.o-forge.io/core/oc-lib/models/common/pricing" +) + +/* +* Workflow Event is a struct that represents a native functiunality. + */ + +type WorkflowEventParams struct { + WorkflowResourceID string `json:"workflow_execution_id" bson:"workflow_execution_id" validate:"required"` + BookingMode *booking.BookingMode `json:"booking_mode" bson:"booking_mode"` +} + +func (wep *WorkflowEventParams) GetBuyingStrategy() pricing.BillingStrategy { + return pricing.BILL_ONCE +} diff --git a/models/resources/priced_resource.go b/models/resources/priced_resource.go index c479220..40409e0 100755 --- a/models/resources/priced_resource.go +++ b/models/resources/priced_resource.go @@ -5,21 +5,37 @@ import ( "fmt" "time" + "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/tools" ) +type BookingConfiguration struct { + 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"` + Mode booking.BookingMode `json:"mode,omitempty" bson:"mode,omitempty"` +} + 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"` - 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"` + Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"` + BookingConfiguration *BookingConfiguration `json:"booking_configuration,omitempty" bson:"booking_configuration,omitempty"` + Variations []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"` + 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) GetQuantity() int { + return abs.Quantity +} + +func (abs *PricedResource) AddQuantity(amount int) { + abs.Quantity += amount } func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF { @@ -46,7 +62,7 @@ func (abs *PricedResource) IsPurchasable() bool { } func (abs *PricedResource) IsBooked() bool { - return true // For dev purposes, prevent that DB objects that don't have a Pricing are considered as not booked + return true // For dev purposes, prevent that DB objects that don't have a Pricing are considered as not booked if abs.SelectedPricing == nil { return false } @@ -54,48 +70,73 @@ func (abs *PricedResource) IsBooked() bool { } func (abs *PricedResource) GetLocationEnd() *time.Time { - return abs.UsageEnd + if abs.BookingConfiguration == nil { + return nil + } + return abs.BookingConfiguration.UsageEnd } func (abs *PricedResource) GetLocationStart() *time.Time { - return abs.UsageStart + if abs.BookingConfiguration == nil { + return nil + } + return abs.BookingConfiguration.UsageStart } func (abs *PricedResource) SetLocationStart(start time.Time) { - abs.UsageStart = &start + if abs.BookingConfiguration == nil { + abs.BookingConfiguration = &BookingConfiguration{} + } + abs.BookingConfiguration.UsageStart = &start } func (abs *PricedResource) SetLocationEnd(end time.Time) { - abs.UsageEnd = &end + if abs.BookingConfiguration == nil { + abs.BookingConfiguration = &BookingConfiguration{} + } + abs.BookingConfiguration.UsageEnd = &end +} + +func (abs *PricedResource) GetBookingMode() booking.BookingMode { + if abs.BookingConfiguration == nil { + return booking.WHEN_POSSIBLE + } + return abs.BookingConfiguration.Mode } func (abs *PricedResource) GetExplicitDurationInS() float64 { - if abs.ExplicitBookingDurationS == 0 { - if abs.UsageEnd == nil && abs.UsageStart == nil { + if abs.BookingConfiguration == nil { + abs.BookingConfiguration = &BookingConfiguration{} + } + if abs.BookingConfiguration.ExplicitBookingDurationS == 0 { + if abs.BookingConfiguration.UsageEnd == nil && abs.BookingConfiguration.UsageStart == nil { return time.Duration(1 * time.Hour).Seconds() } - if abs.UsageEnd == nil { - add := abs.UsageStart.Add(time.Duration(1 * time.Hour)) - abs.UsageEnd = &add + if abs.BookingConfiguration.UsageEnd == nil { + add := abs.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour)) + abs.BookingConfiguration.UsageEnd = &add } - return abs.UsageEnd.Sub(*abs.UsageStart).Seconds() + return abs.BookingConfiguration.UsageEnd.Sub(*abs.BookingConfiguration.UsageStart).Seconds() } - return abs.ExplicitBookingDurationS + return abs.BookingConfiguration.ExplicitBookingDurationS } -func (r *PricedResource) GetPrice() (float64, error) { - fmt.Println("GetPrice", r.UsageStart, r.UsageEnd) +func (r *PricedResource) GetPriceHT() (float64, error) { now := time.Now() - if r.UsageStart == nil { - r.UsageStart = &now + if r.BookingConfiguration == nil { + r.BookingConfiguration = &BookingConfiguration{} } - if r.UsageEnd == nil { - add := r.UsageStart.Add(time.Duration(1 * time.Hour)) - r.UsageEnd = &add + fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd) + if r.BookingConfiguration.UsageStart == nil { + r.BookingConfiguration.UsageStart = &now + } + if r.BookingConfiguration.UsageEnd == nil { + add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour)) + r.BookingConfiguration.UsageEnd = &add } if r.SelectedPricing == nil { 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) + return pricing.GetPriceHT(1, 0, *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations) } diff --git a/models/resources/processing.go b/models/resources/processing.go index 02dac59..dfdc2a3 100755 --- a/models/resources/processing.go +++ b/models/resources/processing.go @@ -56,28 +56,6 @@ func NewProcessingInstance(name string, peerID string) ResourceInstanceITF { UUID: uuid.New().String(), Name: name, }, - Partnerships: []*ResourcePartnerShip[*ProcessingResourcePricingProfile]{ - { - Namespace: "default", - PeerGroups: map[string][]string{ - peerID: {"*"}, - }, - PricingProfiles: map[int]map[int]*ProcessingResourcePricingProfile{ - 0: { - 0: &ProcessingResourcePricingProfile{ - AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ - Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{ - Price: 0, - Currency: "EUR", - TimePricingStrategy: pricing.ONCE, - BuyingStrategy: pricing.PERMANENT, - }, - }, - }, - }, - }, - }, - }, }, } } @@ -92,16 +70,19 @@ func (r *PricedProcessingResource) GetType() tools.DataType { } func (a *PricedProcessingResource) GetExplicitDurationInS() float64 { - if a.ExplicitBookingDurationS == 0 { - if a.IsService || a.UsageStart == nil { + if a.BookingConfiguration == nil { + a.BookingConfiguration = &BookingConfiguration{} + } + if a.BookingConfiguration.ExplicitBookingDurationS == 0 { + if a.IsService || a.BookingConfiguration.UsageStart == nil { if a.IsService { return -1 } return time.Duration(1 * time.Hour).Seconds() } - return a.UsageEnd.Sub(*a.UsageStart).Seconds() + return a.BookingConfiguration.UsageEnd.Sub(*a.BookingConfiguration.UsageStart).Seconds() } - return a.ExplicitBookingDurationS + return a.BookingConfiguration.ExplicitBookingDurationS } func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor { @@ -119,11 +100,3 @@ func (p *ProcessingResourcePricingProfile) IsPurchasable() bool { 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 0f44a67..1916fdd 100755 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -1,9 +1,11 @@ package resources import ( + "errors" "slices" "cloud.o-forge.io/core/oc-lib/config" + "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/common/models" "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/peer" @@ -15,17 +17,30 @@ import ( // AbstractResource is the struct containing all of the attributes commons to all ressources type AbstractResource struct { - utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) - Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource - Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource - Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource - ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource - Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource - UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"` - SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance + utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) + Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource + Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource + Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource + ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource + Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource + UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"` + AllowedBookingModes map[booking.BookingMode]*pricing.PricingVariation `bson:"allowed_booking_modes" json:"allowed_booking_modes"` } -func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF { +func (r *AbstractResource) GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation { + if len(r.AllowedBookingModes) == 0 { + return map[booking.BookingMode]*pricing.PricingVariation{ + booking.PLANNED: { + Percentage: 0, + }, booking.WHEN_POSSIBLE: { + Percentage: 0, + }, + } + } + return r.AllowedBookingModes +} + +func (r *AbstractResource) GetSelectedInstance(selected *int) ResourceInstanceITF { return nil } @@ -57,29 +72,43 @@ func (abs *AbstractInstanciatedResource[T]) AddInstances(instance ResourceInstan abs.Instances = append(abs.Instances, instance.(T)) } -func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { +func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { instances := map[string]string{} profiles := []pricing.PricingProfileITF{} - for _, instance := range abs.Instances { + for _, instance := range abs.Instances { // TODO why it crush before ? instances[instance.GetID()] = instance.GetName() profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups) } var profile pricing.PricingProfileITF - if t := abs.GetSelectedInstance(); t != nil { - profile = t.GetProfile() + if t := abs.GetSelectedInstance(selectedInstance); t != nil { + profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy) } - if profile == nil && len(profiles) > 0 { - profile = profiles[0] + if profile == nil { + if len(profiles) > 0 { + profile = profiles[0] + } else { + if ok, _ := peer.IsMySelf(request.PeerID); ok { + profile = pricing.GetDefaultPricingProfile() + } else { + return nil, errors.New("no pricing profile found") + } + } + } + variations := []*pricing.PricingVariation{} + if selectedBookingModeIndex != nil && abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)] != nil { + variations = append(variations, abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)]) } return &PricedResource{ Name: abs.Name, Logo: abs.Logo, ResourceID: abs.UUID, ResourceType: t, + Quantity: 1, InstancesRefs: instances, SelectedPricing: profile, + Variations: variations, CreatorID: abs.CreatorID, - } + }, nil } func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject { @@ -89,9 +118,9 @@ func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject { return abs } -func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF { - if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex { - return r.Instances[*r.SelectedInstanceIndex] +func (r *AbstractInstanciatedResource[T]) GetSelectedInstance(selected *int) ResourceInstanceITF { + if selected != nil && len(r.Instances) > *selected { + return r.Instances[*selected] } if len(r.Instances) > 0 { return r.Instances[0] @@ -115,8 +144,8 @@ func (d *AbstractInstanciatedResource[T]) Trim() { } } -func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool { - return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request) +func (abs *AbstractInstanciatedResource[T]) VerifyAuth(callName string, request *tools.APIRequest) bool { + return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(callName, request) } func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T { @@ -150,18 +179,22 @@ type GeoPoint 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"` - SelectedBuyingStrategy int `json:"selected_buying_strategy,omitempty" bson:"selected_buying_strategy,omitempty"` - SelectedStrategy int `json:"selected_strategy,omitempty" bson:"selected_strategy,omitempty"` - Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` + 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"` + // SelectedBuyingStrategy int `json:"selected_buying_strategy,omitempty" bson:"selected_buying_strategy,omitempty"` + // SelectedStrategy int `json:"selected_strategy,omitempty" bson:"selected_strategy,omitempty"` + + Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` } +// TODO should kicks all selection + func NewInstance[T ResourcePartnerITF](name string) *ResourceInstance[T] { return &ResourceInstance[T]{ AbstractObject: utils.AbstractObject{ @@ -178,40 +211,27 @@ func (ri *ResourceInstance[T]) ClearEnv() { ri.Outputs = []models.Param{} } -func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF { - if len(ri.Partnerships) > ri.SelectedPartnershipIndex { - prts := ri.Partnerships[ri.SelectedPartnershipIndex] - return prts.GetProfile(ri.SelectedBuyingStrategy, ri.SelectedBuyingStrategy) +func (ri *ResourceInstance[T]) GetProfile(peerID string, partnershipIndex *int, buyingIndex *int, strategyIndex *int) pricing.PricingProfileITF { + if partnershipIndex != nil && len(ri.Partnerships) > *partnershipIndex { + prts := ri.Partnerships[*partnershipIndex] + return prts.GetProfile(buyingIndex, strategyIndex) + } + if ok, _ := peer.IsMySelf(peerID); ok { + return pricing.GetDefaultPricingProfile() } return nil } -func (ri *ResourceInstance[T]) GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF { - if len(ri.Partnerships) > ri.SelectedPartnershipIndex { - return ri.Partnerships[ri.SelectedPartnershipIndex] - } - return nil -} - -func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF { - partners := []ResourcePartnerITF{} - for _, p := range ri.Partnerships { - if p.GetPeerGroups()[peerID] != nil { - for _, g := range p.GetPeerGroups()[peerID] { - if slices.Contains(groups, g) { - partners = append(partners, p) - } - } - } - } - return partners -} - 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)...) } + if len(pricings) == 0 { + if ok, _ := peer.IsMySelf(peerID); ok { + pricings = append(pricings, pricing.GetDefaultPricingProfile()) + } + } return pricings } @@ -222,6 +242,15 @@ func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[stri partners = append(partners, p) groups = append(groups, p.GetPeerGroups()) } + if len(groups) == 0 { + _, id := peer.GetSelf() + groups = []map[string][]string{ + { + id: {"*"}, + }, + } + // TODO make allow all only for self. + } return partners, groups } @@ -238,12 +267,15 @@ type ResourcePartnerShip[T pricing.PricingProfileITF] struct { // to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy } -func (ri *ResourcePartnerShip[T]) GetProfile(buying int, strategy int) pricing.PricingProfileITF { - if strat, ok := ri.PricingProfiles[buying]; ok { - if profile, ok := strat[strategy]; ok { - return profile +func (ri *ResourcePartnerShip[T]) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF { + if buying != nil && strategy != nil { + if strat, ok := ri.PricingProfiles[*buying]; ok { + if profile, ok := strat[*strategy]; ok { + return profile + } } } + return nil } @@ -269,10 +301,21 @@ func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []st return profiles } } + if len(profiles) == 0 { + if ok, _ := peer.IsMySelf(peerID); ok { + profiles = append(profiles, pricing.GetDefaultPricingProfile()) + } + } return profiles } func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string { + if len(rp.PeerGroups) == 0 { + _, id := peer.GetSelf() + return map[string][]string{ + id: {"*"}, + } + } return rp.PeerGroups } diff --git a/models/resources/resource_accessor.go b/models/resources/resource_accessor.go index 2082998..caad2d3 100755 --- a/models/resources/resource_accessor.go +++ b/models/resources/resource_accessor.go @@ -20,7 +20,7 @@ func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIReques if !slices.Contains([]tools.DataType{ tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE, tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE, - tools.DATA_RESOURCE, + tools.DATA_RESOURCE, tools.NATIVE_TOOL, }, t) { return nil } diff --git a/models/resources/storage.go b/models/resources/storage.go index 5993ee1..805275c 100755 --- a/models/resources/storage.go +++ b/models/resources/storage.go @@ -31,15 +31,18 @@ func (r *StorageResource) GetType() string { return tools.STORAGE_RESOURCE.String() } -func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { +func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { if t != tools.STORAGE_RESOURCE { - return nil + return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Storage") + } + p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request) + if err != nil { + return nil, err } - p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request) priced := p.(*PricedResource) return &PricedStorageResource{ PricedResource: *priced, - } + }, nil } type StorageResourceInstance struct { @@ -62,32 +65,6 @@ func NewStorageResourceInstance(name string, peerID string) ResourceInstanceITF UUID: uuid.New().String(), Name: name, }, - Partnerships: []*StorageResourcePartnership{ - { - ResourcePartnerShip: ResourcePartnerShip[*StorageResourcePricingProfile]{ - Namespace: "default", - PeerGroups: map[string][]string{ - peerID: {"*"}, - }, - PricingProfiles: map[int]map[int]*StorageResourcePricingProfile{ - 0: { - 0: &StorageResourcePricingProfile{ - ExploitPricingProfile: pricing.ExploitPricingProfile[StorageResourcePricingStrategy]{ - AccessPricingProfile: pricing.AccessPricingProfile[StorageResourcePricingStrategy]{ - Pricing: pricing.PricingStrategy[StorageResourcePricingStrategy]{ - Price: 0, - Currency: "EUR", - TimePricingStrategy: pricing.ONCE, - BuyingStrategy: pricing.PERMANENT, - }, - }, - }, - }, - }, - }, - }, - }, - }, }, } } @@ -192,10 +169,6 @@ 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 } @@ -207,10 +180,6 @@ func (p *StorageResourcePricingProfile) IsBooked() bool { return true } -func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) { - return p.Pricing.GetPrice(amountOfData, val, start, &end) -} - type PricedStorageResource struct { PricedResource UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"` @@ -220,15 +189,18 @@ func (r *PricedStorageResource) GetType() tools.DataType { return tools.STORAGE_RESOURCE } -func (r *PricedStorageResource) GetPrice() (float64, error) { - fmt.Println("GetPrice", r.UsageStart, r.UsageEnd) - now := time.Now() - if r.UsageStart == nil { - r.UsageStart = &now +func (r *PricedStorageResource) GetPriceHT() (float64, error) { + if r.BookingConfiguration == nil { + r.BookingConfiguration = &BookingConfiguration{} } - if r.UsageEnd == nil { - add := r.UsageStart.Add(time.Duration(1 * time.Hour)) - r.UsageEnd = &add + fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd) + now := time.Now() + if r.BookingConfiguration.UsageStart == nil { + r.BookingConfiguration.UsageStart = &now + } + if r.BookingConfiguration.UsageEnd == nil { + add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour)) + r.BookingConfiguration.UsageEnd = &add } if r.SelectedPricing == nil { return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID) @@ -242,5 +214,6 @@ func (r *PricedStorageResource) GetPrice() (float64, error) { return 0, err } } - return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd) + return pricing.GetPriceHT(amountOfData, r.BookingConfiguration.ExplicitBookingDurationS, + *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations) } diff --git a/models/resources/tests/compute_test.go b/models/resources/tests/compute_test.go index 00c764f..812c0bc 100644 --- a/models/resources/tests/compute_test.go +++ b/models/resources/tests/compute_test.go @@ -30,12 +30,12 @@ func TestComputeResource_ConvertToPricedResource(t *testing.T) { cr := &resources.ComputeResource{} cr.UUID = "comp123" cr.AbstractInstanciatedResource.UUID = cr.UUID - result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req) + result, _ := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, nil, nil, nil, nil, nil, req) assert.NotNil(t, result) assert.IsType(t, &resources.PricedComputeResource{}, result) } -func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) { +func TestComputeResourcePricingProfile_GetPriceHT_CPUs(t *testing.T) { start := time.Now() end := start.Add(1 * time.Hour) profile := resources.ComputeResourcePricingProfile{ @@ -47,45 +47,47 @@ func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) { }, } - price, err := profile.GetPrice(2, 3600, start, end, "cpus", "Xeon") + price, err := profile.GetPriceHT(2, 3600, start, end, []*pricing.PricingVariation{}, "cpus", "Xeon") require.NoError(t, err) assert.Greater(t, price, float64(0)) } -func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) { +func TestComputeResourcePricingProfile_GetPriceHT_InvalidParams(t *testing.T) { profile := resources.ComputeResourcePricingProfile{} - _, err := profile.GetPrice(1, 3600, time.Now(), time.Now()) + _, err := profile.GetPriceHT(1, 3600, time.Now(), time.Now(), []*pricing.PricingVariation{}) assert.Error(t, err) assert.Equal(t, "params must be set", err.Error()) } -func TestPricedComputeResource_GetPrice(t *testing.T) { +func TestPricedComputeResource_GetPriceHT(t *testing.T) { start := time.Now() end := start.Add(1 * time.Hour) r := resources.PricedComputeResource{ PricedResource: resources.PricedResource{ - ResourceID: "comp456", - UsageStart: &start, - UsageEnd: &end, - ExplicitBookingDurationS: 3600, + ResourceID: "comp456", + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &start, + UsageEnd: &end, + ExplicitBookingDurationS: 3600, + }, }, CPUsLocated: map[string]float64{"Xeon": 2}, GPUsLocated: map[string]float64{"Tesla": 1}, RAMLocated: 4, } - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.NoError(t, err) assert.Greater(t, price, float64(0)) } -func TestPricedComputeResource_GetPrice_MissingProfile(t *testing.T) { +func TestPricedComputeResource_GetPriceHT_MissingProfile(t *testing.T) { r := resources.PricedComputeResource{ PricedResource: resources.PricedResource{ ResourceID: "comp789", }, } - _, err := r.GetPrice() + _, err := r.GetPriceHT() require.Error(t, err) assert.Contains(t, err.Error(), "pricing profile must be set") } diff --git a/models/resources/tests/data_test.go b/models/resources/tests/data_test.go index b8a3380..035ed93 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{}) + res, _ := d.ConvertToPricedResource(tools.DATA_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{}) assert.IsType(t, &resources.PricedDataResource{}, res) - nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{}) + nilRes, _ := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{}) assert.Nil(t, nilRes) } @@ -80,7 +80,7 @@ func TestDataResourcePricingProfile_IsPurchased(t *testing.T) { assert.True(t, profile.IsPurchasable()) } -func TestPricedDataResource_GetPrice(t *testing.T) { +func TestPricedDataResource_GetPriceHT(t *testing.T) { now := time.Now() later := now.Add(1 * time.Hour) mockPrice := 42.0 @@ -92,23 +92,25 @@ func TestPricedDataResource_GetPrice(t *testing.T) { r := &resources.PricedDataResource{ PricedResource: resources.PricedResource{ - UsageStart: &now, - UsageEnd: &later, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &now, + UsageEnd: &later, + }, }, } - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.NoError(t, err) assert.Equal(t, mockPrice, price) } -func TestPricedDataResource_GetPrice_NoProfiles(t *testing.T) { +func TestPricedDataResource_GetPriceHT_NoProfiles(t *testing.T) { r := &resources.PricedDataResource{ PricedResource: resources.PricedResource{ ResourceID: "test-resource", }, } - _, err := r.GetPrice() + _, err := r.GetPriceHT() assert.Error(t, err) assert.Contains(t, err.Error(), "pricing profile must be set") } diff --git a/models/resources/tests/priced_resource_test.go b/models/resources/tests/priced_resource_test.go index 28f3f4c..a2a2c92 100644 --- a/models/resources/tests/priced_resource_test.go +++ b/models/resources/tests/priced_resource_test.go @@ -26,7 +26,7 @@ func (m *MockPricingProfile) IsPurchasable() bool { return m.Purchased } -func (m *MockPricingProfile) GetPrice(amount float64, explicitDuration float64, start time.Time, end time.Time, _ ...string) (float64, error) { +func (m *MockPricingProfile) GetPriceHT(amount float64, explicitDuration float64, start time.Time, end time.Time, variations []*pricing.PricingVariation, _ ...string) (float64, error) { if m.ReturnErr { return 0, errors.New("mock error") } @@ -72,14 +72,21 @@ func TestGetAndSetLocationStartEnd(t *testing.T) { func TestGetExplicitDurationInS(t *testing.T) { t.Run("uses explicit duration if set", func(t *testing.T) { - r := &resources.PricedResource{ExplicitBookingDurationS: 3600} + r := &resources.PricedResource{BookingConfiguration: &resources.BookingConfiguration{ + ExplicitBookingDurationS: 3600, + }, + } assert.Equal(t, 3600.0, r.GetExplicitDurationInS()) }) t.Run("computes duration from start and end", func(t *testing.T) { start := time.Now() end := start.Add(2 * time.Hour) - r := &resources.PricedResource{UsageStart: &start, UsageEnd: &end} + r := &resources.PricedResource{ + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &start, UsageEnd: &end, + }, + } assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1) }) @@ -89,10 +96,10 @@ func TestGetExplicitDurationInS(t *testing.T) { }) } -func TestGetPrice(t *testing.T) { +func TestGetPriceHT(t *testing.T) { t.Run("returns error if no pricing profile", func(t *testing.T) { r := &resources.PricedResource{ResourceID: "no-profile"} - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.Error(t, err) assert.Contains(t, err.Error(), "pricing profile must be set") assert.Equal(t, 0.0, price) @@ -102,24 +109,28 @@ func TestGetPrice(t *testing.T) { start := time.Now() end := start.Add(30 * time.Minute) r := &resources.PricedResource{ - UsageStart: &start, - UsageEnd: &end, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &start, + UsageEnd: &end, + }, } - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.NoError(t, err) assert.Equal(t, 42.0, price) }) - t.Run("returns error if profile GetPrice fails", func(t *testing.T) { + t.Run("returns error if profile GetPriceHT fails", func(t *testing.T) { start := time.Now() end := start.Add(1 * time.Hour) mock := &MockPricingProfile{ReturnErr: true} r := &resources.PricedResource{ SelectedPricing: mock, - UsageStart: &start, - UsageEnd: &end, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &start, + UsageEnd: &end, + }, } - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.Error(t, err) assert.Equal(t, 0.0, price) }) @@ -130,10 +141,12 @@ func TestGetPrice(t *testing.T) { mock := &MockPricingProfile{ReturnCost: 10.0} r := &resources.PricedResource{ SelectedPricing: mock, - UsageStart: &start, - UsageEnd: &end, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &start, + UsageEnd: &end, + }, } - price, err := r.GetPrice() + price, err := r.GetPriceHT() require.NoError(t, err) assert.Equal(t, 10.0, price) }) diff --git a/models/resources/tests/processing_test.go b/models/resources/tests/processing_test.go index ce3de34..59ba5aa 100644 --- a/models/resources/tests/processing_test.go +++ b/models/resources/tests/processing_test.go @@ -5,6 +5,7 @@ import ( "time" "cloud.o-forge.io/core/oc-lib/models/common/pricing" + "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/tools" "github.com/stretchr/testify/assert" @@ -40,7 +41,9 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) { name: "Nil start time, non-service", input: PricedProcessingResource{ PricedResource: PricedResource{ - UsageStart: nil, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: nil, + }, }, }, expected: float64((1 * time.Hour).Seconds()), @@ -49,8 +52,10 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) { name: "Duration computed from start and end", input: PricedProcessingResource{ PricedResource: PricedResource{ - UsageStart: &now, - UsageEnd: &after, + BookingConfiguration: &resources.BookingConfiguration{ + UsageStart: &now, + UsageEnd: &after, + }, }, }, expected: float64((2 * time.Hour).Seconds()), @@ -59,7 +64,9 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) { name: "Explicit duration takes precedence", input: PricedProcessingResource{ PricedResource: PricedResource{ - ExplicitBookingDurationS: 1337, + BookingConfiguration: &resources.BookingConfiguration{ + ExplicitBookingDurationS: 1337, + }, }, }, expected: 1337, @@ -80,7 +87,7 @@ func TestProcessingResource_GetAccessor(t *testing.T) { assert.NotNil(t, acc) } -func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) { +func TestProcessingResourcePricingProfile_GetPriceHT(t *testing.T) { start := time.Now() end := start.Add(2 * time.Hour) mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ @@ -88,8 +95,8 @@ func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) { Price: 100.0, }, } - profile := &ProcessingResourcePricingProfile{mockPricing} - price, err := profile.GetPrice(0, 0, start, end) + profile := &ProcessingResourcePricingProfile{AccessPricingProfile: mockPricing} + price, err := profile.GetPriceHT(0, 0, start, end, []*pricing.PricingVariation{}) assert.NoError(t, err) assert.Equal(t, 100.0, price) } diff --git a/models/resources/tests/resource_test.go b/models/resources/tests/resource_test.go index 30acd53..414eaec 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) GetProfile() pricing.PricingProfileITF { +func (m *MockInstance) GetProfile(peerID string, a *int, b *int, c *int) pricing.PricingProfileITF { return nil } func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF { @@ -36,7 +36,7 @@ type MockPartner struct { groups map[string][]string } -func (m *MockPartner) GetProfile(buying int, strategy int) pricing.PricingProfileITF { +func (m *MockPartner) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF { return nil } @@ -62,10 +62,10 @@ func TestGetSelectedInstance_WithValidIndex(t *testing.T) { inst1 := &MockInstance{ID: "1"} inst2 := &MockInstance{ID: "2"} resource := &resources.AbstractInstanciatedResource[*MockInstance]{ - AbstractResource: resources.AbstractResource{SelectedInstanceIndex: &index}, + AbstractResource: resources.AbstractResource{}, Instances: []*MockInstance{inst1, inst2}, } - result := resource.GetSelectedInstance() + result := resource.GetSelectedInstance(&index) assert.Equal(t, inst2, result) } @@ -74,7 +74,7 @@ func TestGetSelectedInstance_NoIndex(t *testing.T) { resource := &resources.AbstractInstanciatedResource[*MockInstance]{ Instances: []*MockInstance{inst}, } - result := resource.GetSelectedInstance() + result := resource.GetSelectedInstance(nil) assert.Equal(t, inst, result) } @@ -103,9 +103,9 @@ type FakeResource struct { resources.AbstractInstanciatedResource[*MockInstance] } -func (f *FakeResource) Trim() {} -func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {} -func (f *FakeResource) VerifyAuth(*tools.APIRequest) bool { return true } +func (f *FakeResource) Trim() {} +func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {} +func (f *FakeResource) VerifyAuth(string, *tools.APIRequest) bool { return true } func TestNewAccessor_ReturnsValid(t *testing.T) { acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject { diff --git a/models/resources/tests/storage_test.go b/models/resources/tests/storage_test.go index bf2b8c7..22da67e 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{}) + priced, _ := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, nil, nil, nil, nil, nil, &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{}) + priced, _ := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{}) assert.Nil(t, priced) } @@ -94,12 +94,12 @@ func TestStorageResourcePricingStrategy_GetQuantity_Invalid(t *testing.T) { assert.Equal(t, 0.0, q) } -func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) { +func TestPricedStorageResource_GetPriceHT_NoProfiles(t *testing.T) { res := &resources.PricedStorageResource{ PricedResource: resources.PricedResource{ ResourceID: "res-id", }, } - _, err := res.GetPrice() + _, err := res.GetPriceHT() assert.Error(t, err) } diff --git a/models/resources/tests/workflow_test.go b/models/resources/tests/workflow_test.go index 5d8eeb9..2fbd4b4 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) + pr, _ := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, nil, nil, nil, nil, nil, 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 904b9fa..6f84813 100755 --- a/models/resources/workflow.go +++ b/models/resources/workflow.go @@ -37,12 +37,13 @@ func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) { /* EMPTY */ } -func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { +func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { return &PricedResource{ Name: w.Name, Logo: w.Logo, ResourceID: w.UUID, ResourceType: t, + Quantity: 1, CreatorID: w.CreatorID, - } + }, nil // TODO ??? } diff --git a/models/utils/abstracts.go b/models/utils/abstracts.go index 11f0ce2..8776cb8 100755 --- a/models/utils/abstracts.go +++ b/models/utils/abstracts.go @@ -98,8 +98,8 @@ func (ao *AbstractObject) UpToDate(user string, peer string, create bool) { } } -func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool { - return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "") +func (ao *AbstractObject) VerifyAuth(callName string, request *tools.APIRequest) bool { + return (ao.AccessMode == Public && callName == "get") || request.Admin || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "") } func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters { diff --git a/models/utils/common.go b/models/utils/common.go index 7259cc5..120e73b 100755 --- a/models/utils/common.go +++ b/models/utils/common.go @@ -18,7 +18,7 @@ func VerifyAccess(a Accessor, id string) error { if err != nil { return err } - if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { + if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) { return errors.New("you are not allowed to access :" + a.GetType().String()) } return nil @@ -41,7 +41,7 @@ func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) { }}, }, } - if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { + if a.ShouldVerifyAuth() && !data.VerifyAuth("store", a.GetRequest()) { return nil, 403, errors.New("you are not allowed to access : " + a.GetType().String()) } if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 { @@ -68,7 +68,7 @@ func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) { if err != nil { return nil, code, err } - if a.ShouldVerifyAuth() && !res.VerifyAuth(a.GetRequest()) { + if a.ShouldVerifyAuth() && !res.VerifyAuth("delete", a.GetRequest()) { return nil, 403, errors.New("you are not allowed to access " + a.GetType().String()) } _, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String()) @@ -92,7 +92,7 @@ func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObje } set = newSet r.UpToDate(a.GetUser(), a.GetPeerID(), false) - if a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest()) { + if a.ShouldVerifyAuth() && !r.VerifyAuth("update", a.GetRequest()) { return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) } change := set.Serialize(set) // get the changes @@ -116,7 +116,7 @@ func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, erro return nil, code, err } res_mongo.Decode(&data) - if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { + if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) { return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) } return f(data) @@ -132,7 +132,7 @@ func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft return nil, 404, err } for _, r := range results { - if (a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) { + if (a.ShouldVerifyAuth() && !r.VerifyAuth("get", a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) { continue } objs = append(objs, f(r)) diff --git a/models/utils/interfaces.go b/models/utils/interfaces.go index 11d1f39..f40c2cc 100755 --- a/models/utils/interfaces.go +++ b/models/utils/interfaces.go @@ -28,7 +28,7 @@ type DBObject interface { GetCreatorID() string UpToDate(user string, peer string, create bool) CanUpdate(set DBObject) (bool, DBObject) - VerifyAuth(request *tools.APIRequest) bool + VerifyAuth(callName string, request *tools.APIRequest) bool Serialize(obj DBObject) map[string]interface{} GetAccessor(request *tools.APIRequest) Accessor Deserialize(j map[string]interface{}, obj DBObject) DBObject diff --git a/models/utils/tests/abstracts_test.go b/models/utils/tests/abstracts_test.go index 83c9c33..f83c7cf 100644 --- a/models/utils/tests/abstracts_test.go +++ b/models/utils/tests/abstracts_test.go @@ -87,14 +87,14 @@ func TestUpToDate_CreateTrue(t *testing.T) { func TestVerifyAuth(t *testing.T) { request := &tools.APIRequest{PeerID: "peer123"} ao := &utils.AbstractObject{CreatorID: "peer123"} - assert.True(t, ao.VerifyAuth(request)) + assert.True(t, ao.VerifyAuth("get", request)) ao = &utils.AbstractObject{AccessMode: utils.Public} - assert.True(t, ao.VerifyAuth(nil)) + assert.True(t, ao.VerifyAuth("get", nil)) ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"} request = &tools.APIRequest{PeerID: "wrong"} - assert.False(t, ao.VerifyAuth(request)) + assert.False(t, ao.VerifyAuth("get", request)) } func TestGetObjectFilters(t *testing.T) { diff --git a/models/workflow/graph/graph.go b/models/workflow/graph/graph.go index 577036a..05e4324 100644 --- a/models/workflow/graph/graph.go +++ b/models/workflow/graph/graph.go @@ -47,6 +47,10 @@ func (wf *Graph) IsProcessing(item GraphItem) bool { return item.Processing != nil } +func (wf *Graph) IsNativeTool(item GraphItem) bool { + return item.NativeTool != nil +} + func (wf *Graph) IsCompute(item GraphItem) bool { return item.Compute != nil } @@ -64,7 +68,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) (float64, float64) { + f func(GraphItem) resources.ResourceInterface, instance int, bookingMode int, request *tools.APIRequest) (float64, float64, error) { nearestStart := float64(10000000000) oneIsInfinite := false longestDuration := float64(0) @@ -76,7 +80,10 @@ 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) + priced, err := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &instance, &bookingMode, request) + if err != nil { + return 0, 0, err + } if source != "" { if priced.GetLocationStart() != nil { near := float64(priced.GetLocationStart().Sub(start).Seconds()) @@ -97,15 +104,15 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce } } if oneIsInfinite { - return nearestStart, -1 + return nearestStart, -1, nil } - return nearestStart, longestDuration + return nearestStart, longestDuration, nil } /* * 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) float64 { +func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, instance int, bookingMode int, request *tools.APIRequest) (float64, error) { currents := []float64{} // list of current time for _, link := range g.Links { // for each link var source string // source is the source of the link @@ -121,13 +128,20 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI if r == nil { // if item is nil, continue continue } - priced := r.ConvertToPricedResource(dt, request) + priced, err := r.ConvertToPricedResource(dt, &instance, &bookingMode, request) + if err != nil { + return 0, err + } 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 + return current, nil } - 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 + add, err := g.GetAverageTimeProcessingBeforeStart(current, source, instance, bookingMode, request) + if err != nil { + return 0, err + } + current += add // 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 { @@ -135,12 +149,14 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI max = current } } - return max + return max, nil } func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) { if item, ok := g.Items[id]; ok { if item.Data != nil { + return tools.NATIVE_TOOL, item.NativeTool + } else if item.Data != nil { return tools.DATA_RESOURCE, item.Data } else if item.Compute != nil { return tools.COMPUTE_RESOURCE, item.Compute diff --git a/models/workflow/workflow.go b/models/workflow/workflow.go index 0eebd44..f9738ba 100644 --- a/models/workflow/workflow.go +++ b/models/workflow/workflow.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/common/pricing" @@ -20,6 +21,16 @@ import ( "github.com/google/uuid" ) +type ConfigItem map[string]int + +func (c ConfigItem) Get(key string) *int { + i := 0 + if ins, ok := c[key]; ok { + i = ins + } + return &i +} + /* * Workflow is a struct that represents a workflow * it defines the native workflow @@ -40,6 +51,11 @@ func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *Workflow) GetResources(dt tools.DataType) []resources.ResourceInterface { itf := []resources.ResourceInterface{} switch dt { + case tools.NATIVE_TOOL: + for _, d := range d.NativeTools { + itf = append(itf, d) + } + return itf case tools.DATA_RESOURCE: for _, d := range d.DataResources { itf = append(itf, d) @@ -134,19 +150,25 @@ func (d *Workflow) ExtractFromPlantUML(plantUML multipart.File, request *tools.A graphVarName[varName] = graphItem continue } else if strings.Contains(line, n+"-->") { - err := d.extractLink(line, graphVarName, "-->") + err := d.extractLink(line, graphVarName, "-->", false) + if err != nil { + fmt.Println(err) + continue + } + } else if strings.Contains(line, n+"<--") { + err := d.extractLink(line, graphVarName, "<--", true) if err != nil { fmt.Println(err) continue } } else if strings.Contains(line, n+"--") { - err := d.extractLink(line, graphVarName, "--") + err := d.extractLink(line, graphVarName, "--", false) if err != nil { fmt.Println(err) continue } } else if strings.Contains(line, n+"-") { - err := d.extractLink(line, graphVarName, "-") + err := d.extractLink(line, graphVarName, "-", false) if err != nil { fmt.Println(err) continue @@ -176,7 +198,7 @@ func (d *Workflow) generateResource(datas []resources.ResourceInterface, request return nil } -func (d *Workflow) extractLink(line string, graphVarName map[string]*graph.GraphItem, pattern string) error { +func (d *Workflow) extractLink(line string, graphVarName map[string]*graph.GraphItem, pattern string, reverse bool) error { splitted := strings.Split(line, pattern) if len(splitted) < 2 { return errors.New("links elements not found") @@ -199,6 +221,11 @@ func (d *Workflow) extractLink(line string, graphVarName map[string]*graph.Graph Y: 0, }, } + if reverse { + tmp := link.Destination + link.Destination = link.Source + link.Source = tmp + } splittedComments := strings.Split(line, "'") if len(splittedComments) <= 1 { return errors.New("Can't deserialize Object, there's no commentary") @@ -256,10 +283,19 @@ func (d *Workflow) getNewGraphItem(dataName string, graphItem *graph.GraphItem, d.Datas = append(d.Datas, resource.GetID()) d.DataResources = append(d.DataResources, resource.(*resources.DataResource)) graphItem.Data = resource.(*resources.DataResource) - case "Processing", "Event": + case "Processing": d.Processings = append(d.Processings, resource.GetID()) d.ProcessingResources = append(d.ProcessingResources, resource.(*resources.ProcessingResource)) graphItem.Processing = resource.(*resources.ProcessingResource) + case "Event": + access := resources.NewAccessor[*resources.NativeTool](tools.NATIVE_TOOL, &tools.APIRequest{ + Admin: true, + }, func() utils.DBObject { return &resources.NativeTool{} }) + t, _, err := access.Search(nil, "WORKFLOW_EVENT", false) + if err == nil && len(t) > 0 { + d.NativeTool = append(d.NativeTool, t[0].GetID()) + graphItem.NativeTool = t[0].(*resources.NativeTool) + } case "Storage": d.Storages = append(d.Storages, resource.GetID()) d.StorageResources = append(d.StorageResources, resource.(*resources.StorageResource)) @@ -312,6 +348,12 @@ func (w *Workflow) IsDependancy(id string) []Deps { return dependancyOfIDs } +func (w *Workflow) GetFirstItems() []graph.GraphItem { + return w.GetGraphItems(func(item graph.GraphItem) bool { + return len(w.GetDependencies(w.GetID())) == 0 + }) +} + func (w *Workflow) GetDependencies(id string) (dependencies []Deps) { for _, link := range w.Graph.Links { if _, ok := w.Graph.Items[link.Source.ID]; !ok { @@ -336,16 +378,26 @@ func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas } func (w *Workflow) GetPricedItem( - f func(item graph.GraphItem) bool, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) map[string]pricing.PricedItemITF { + f func(item graph.GraphItem) bool, request *tools.APIRequest, + instance int, + partnership int, + buying int, + strategy int, + bookingMode int, + buyingStrategy int, + pricingStrategy int) (map[string]pricing.PricedItemITF, error) { list_datas := map[string]pricing.PricedItemITF{} for _, item := range w.Graph.Items { if f(item) { dt, res := item.GetResource() - ord := res.ConvertToPricedResource(dt, request) + ord, err := res.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request) + if err != nil { + return list_datas, err + } list_datas[res.GetID()] = ord } } - return list_datas + return list_datas, nil } type Related struct { @@ -378,7 +430,7 @@ func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph return related } -func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool { +func (ao *Workflow) VerifyAuth(callName string, request *tools.APIRequest) bool { isAuthorized := false if len(ao.Shared) > 0 { for _, shared := range ao.Shared { @@ -386,11 +438,11 @@ func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool { if code != 200 || shared == nil { isAuthorized = false } else { - isAuthorized = shared.VerifyAuth(request) + isAuthorized = shared.VerifyAuth(callName, request) } } } - return ao.AbstractObject.VerifyAuth(request) || isAuthorized + return ao.AbstractObject.VerifyAuth(callName, request) || isAuthorized } /* @@ -422,65 +474,106 @@ 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) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) { +func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, request *tools.APIRequest) (bool, 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, wf.Graph.IsProcessing, - func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { - return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS() - }, func(started time.Time, duration float64) *time.Time { + ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, wf.Graph.IsProcessing, + func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) { + d, err := wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), + *instances.Get(res.GetID()), *partnerships.Get(res.GetID()), *buyings.Get(res.GetID()), *strategies.Get(res.GetID()), + bookingMode, request) + if err != nil { + return start, 0, err + } + return start.Add(time.Duration(d) * time.Second), priced.GetExplicitDurationInS(), nil + }, func(started time.Time, duration float64) (*time.Time, error) { s := started.Add(time.Duration(duration)) - return &s + return &s, nil }) if err != nil { - return 0, priceds, nil, err + return false, 0, priceds, nil, err } - if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, - wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { - return start, 0 - }, func(started time.Time, duration float64) *time.Time { - return end + if _, priceds, err = plan[resources.ResourceInterface](tools.NATIVE_TOOL, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, + wf.Graph.IsNativeTool, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) { + return start, 0, nil + }, func(started time.Time, duration float64) (*time.Time, error) { + return end, nil }); err != nil { - return 0, priceds, nil, err + return false, 0, priceds, nil, err } - for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} { - if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request, - f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { - nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) { + if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, + wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) { + return start, 0, nil + }, func(started time.Time, duration float64) (*time.Time, error) { + return end, nil + }); err != nil { + return false, 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, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, + f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) { + nearestStart, longestDuration, err := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) { if f(i) { _, r = i.GetResource() } return r - }, request) - return start.Add(time.Duration(nearestStart) * time.Second), longestDuration - }, func(started time.Time, duration float64) *time.Time { + }, *instances.Get(res.GetID()), *partnerships.Get(res.GetID()), + *buyings.Get(res.GetID()), *strategies.Get(res.GetID()), bookingMode, request) + if err != nil { + return start, longestDuration, err + } + return start.Add(time.Duration(nearestStart) * time.Second), longestDuration, nil + }, func(started time.Time, duration float64) (*time.Time, error) { s := started.Add(time.Duration(duration)) - return &s + return &s, nil }); err != nil { - return 0, priceds, nil, err + return false, 0, priceds, nil, err } } longest := common.GetPlannerLongestTime(end, priceds, request) - if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow, - func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { + if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, instances, partnerships, buyings, strategies, + bookingMode, wf, priceds, request, wf.Graph.IsWorkflow, + func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) { start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second) longest := float64(-1) r, code, err := res.GetAccessor(request).LoadOne(res.GetID()) if code != 200 || err != nil { - return start, longest + return start, longest, err } - if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil { - return start, longest + _, neoLongest, priceds2, _, err := r.(*Workflow).Planify(start, end, instances, partnerships, buyings, strategies, bookingMode, request) + // should ... import priced + if err != nil { + return start, longest, err } else if neoLongest > longest { longest = neoLongest } - return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest - }, func(start time.Time, longest float64) *time.Time { + for k, v := range priceds2 { + if priceds[k] == nil { + priceds[k] = map[string]pricing.PricedItemITF{} + } + for k2, v2 := range v { + if priceds[k][k2] != nil { + v2.AddQuantity(priceds[k][k2].GetQuantity()) + } + + } + } + return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest, nil + }, func(start time.Time, longest float64) (*time.Time, error) { s := start.Add(time.Duration(longest) * time.Second) - return &s + return &s, nil }); err != nil { - return 0, priceds, nil, err + return false, 0, priceds, nil, err } - return longest, priceds, wf, nil + isPreemptible := true + for _, first := range wf.GetFirstItems() { + _, res := first.GetResource() + if res.GetBookingModes()[booking.PREEMPTED] == nil { + isPreemptible = false + break + } + } + return isPreemptible, longest, priceds, wf, nil } // Returns a map of DataType (processing,computing,data,storage,worfklow) where each resource (identified by its UUID) @@ -511,8 +604,10 @@ func (w *Workflow) GetItemsByResources() map[tools.DataType]map[string][]string } func plan[T resources.ResourceInterface]( - dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest, - f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) { + dt tools.DataType, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest, + f func(graph.GraphItem) bool, + start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64, error), + end func(time.Time, float64) (*time.Time, error)) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) { resources := []T{} for _, item := range wf.GetGraphItems(f) { if priceds[dt] == nil { @@ -522,23 +617,34 @@ func plan[T resources.ResourceInterface]( if realItem == nil { return resources, priceds, errors.New("could not load the processing resource") } - priced := realItem.ConvertToPricedResource(dt, request) + priced, err := realItem.ConvertToPricedResource(dt, instances.Get(realItem.GetID()), + partnerships.Get(realItem.GetID()), buyings.Get(realItem.GetID()), strategies.Get(realItem.GetID()), &bookingMode, request) + if err != nil { + return resources, priceds, err + } // Should be commented once the Pricing selection feature has been implemented, related to the commit d35ad440fa77763ec7f49ab34a85e47e75581b61 // if priced.SelectPricing() == nil { // return resources, priceds, errors.New("no pricings are selected... can't proceed") // } - started, duration := start(realItem, priced) + started, duration, err := start(realItem, priced) + if err != nil { + return resources, priceds, err + } priced.SetLocationStart(started) if duration >= 0 { - if e := end(started, duration); e != nil { + if e, err := end(started, duration); err == nil && e != nil { priced.SetLocationEnd(*e) } } - if e := end(started, priced.GetExplicitDurationInS()); e != nil { + if e, err := end(started, priced.GetExplicitDurationInS()); err != nil && e != nil { priced.SetLocationEnd(*e) } resources = append(resources, realItem.(T)) + if priceds[dt][item.ID] != nil { + priced.AddQuantity(priceds[dt][item.ID].GetQuantity()) + } priceds[dt][item.ID] = priced + } return resources, priceds, nil } diff --git a/models/workflow_execution/tests/workflow_scheduler_test.go b/models/workflow_execution/tests/workflow_scheduler_test.go index d4ae87c..32e465f 100644 --- a/models/workflow_execution/tests/workflow_scheduler_test.go +++ b/models/workflow_execution/tests/workflow_scheduler_test.go @@ -29,7 +29,7 @@ func TestNewScheduler_ValidInput(t *testing.T) { dur := 7200.0 cronStr := "0 0 * * * *" - sched := workflow_execution.NewScheduler(s, e, dur, cronStr) + sched := workflow_execution.NewScheduler(0, s, e, dur, cronStr) assert.NotNil(t, sched) assert.Equal(t, dur, sched.DurationS) @@ -42,7 +42,7 @@ func TestNewScheduler_InvalidStart(t *testing.T) { dur := 7200.0 cronStr := "0 0 * * * *" - sched := workflow_execution.NewScheduler(s, e, dur, cronStr) + sched := workflow_execution.NewScheduler(0, s, e, dur, cronStr) assert.Nil(t, sched) } @@ -52,7 +52,7 @@ func TestNewScheduler_InvalidEnd(t *testing.T) { dur := 7200.0 cronStr := "0 0 * * * *" - sched := workflow_execution.NewScheduler(s, e, dur, cronStr) + sched := workflow_execution.NewScheduler(0, s, e, dur, cronStr) assert.NotNil(t, sched) assert.Nil(t, sched.End) } @@ -120,7 +120,7 @@ func TestGetExecutions_Success(t *testing.T) { }, } - execs, err := ws.GetExecutions(wf) + execs, err := ws.GetExecutions(wf, false) assert.NoError(t, err) assert.Greater(t, len(execs), 0) assert.Equal(t, wf.UUID, execs[0].WorkflowID) diff --git a/models/workflow_execution/tests/workflow_test.go b/models/workflow_execution/tests/workflow_test.go index 4c93c24..2ca3f9c 100755 --- a/models/workflow_execution/tests/workflow_test.go +++ b/models/workflow_execution/tests/workflow_test.go @@ -93,7 +93,7 @@ func TestGetName_ReturnsCorrectFormat(t *testing.T) { func TestVerifyAuth_AlwaysTrue(t *testing.T) { exec := &workflow_execution.WorkflowExecution{} - assert.True(t, exec.VerifyAuth(nil)) + assert.True(t, exec.VerifyAuth("get", nil)) } func TestUpdateOne_RejectsZeroState(t *testing.T) { diff --git a/models/workflow_execution/workflow_execution.go b/models/workflow_execution/workflow_execution.go index 6bb2818..8e0f2d6 100755 --- a/models/workflow_execution/workflow_execution.go +++ b/models/workflow_execution/workflow_execution.go @@ -23,6 +23,7 @@ import ( */ type WorkflowExecution struct { utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) + Priority int `json:"priority" bson:"priority"` // will best effort on default // Will Best Effort on priority 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"` @@ -108,7 +109,7 @@ func (d *WorkflowExecution) GetAccessor(request *tools.APIRequest) utils.Accesso return NewAccessor(request) // Create a new instance of the accessor } -func (d *WorkflowExecution) VerifyAuth(request *tools.APIRequest) bool { +func (d *WorkflowExecution) VerifyAuth(callName string, request *tools.APIRequest) bool { return true } diff --git a/models/workflow_execution/workflow_scheduler.go b/models/workflow_execution/workflow_scheduler.go index 7dc766b..988323b 100755 --- a/models/workflow_execution/workflow_scheduler.go +++ b/models/workflow_execution/workflow_scheduler.go @@ -37,20 +37,42 @@ 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 + BookingMode booking.BookingMode `json:"booking_mode,omitempty"` // BookingMode qualify the preemption order of the scheduling. if no payment allowed with preemption set up When_Possible + SelectedInstances workflow.ConfigItem `json:"selected_instances"` + SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"` + SelectedBuyings workflow.ConfigItem `json:"selected_buyings"` + SelectedStrategies workflow.ConfigItem `json:"selected_strategies"` + SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"` } -func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule { - s, err := time.Parse("2006-01-02T15:04:05", start) - if err != nil { - return nil - } +// TODO PREEMPTION ! +/* +To schedule a preempted, omg. +pour faire ça on doit alors lancé une exécution prioritaire qui passera devant toutes les autres, celon un niveau de priorité. +Preemptible = 7, pour le moment il n'existera que 0 et 7. +Dans le cas d'une préemption l'exécution est immédiable et bloquera tout le monde tant qu'il n'a pas été exécuté. +Une ressource doit pouvoir être preemptible pour être exécutée de la sorte. +Se qui implique si on est sur une ressource par ressource que si un élement n'est pas préemptible, +alors il devra être effectué dés que possible + +Dans le cas dés que possible, la start date est immédiate MAIS ! +ne pourra se lancé que SI il n'existe pas d'exécution se lançant durant la période indicative. ( Ultra complexe ) +*/ + +func NewScheduler(mode int, start string, end string, durationInS float64, cron string) *WorkflowSchedule { ws := &WorkflowSchedule{ - UUID: uuid.New().String(), - Start: s, - DurationS: durationInS, - Cron: cron, + UUID: uuid.New().String(), + Start: time.Now(), + BookingMode: booking.BookingMode(mode), + DurationS: durationInS, + Cron: cron, } + s, err := time.Parse("2006-01-02T15:04:05", start) + if err == nil && ws.BookingMode == booking.PLANNED { + ws.Start = s // can apply a defined start other than now, if planned + } + e, err := time.Parse("2006-01-02T15:04:05", end) if err == nil { ws.End = &e @@ -68,7 +90,9 @@ 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) + isPreemptible, longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, + ws.SelectedInstances, ws.SelectedPartnerships, ws.SelectedBuyings, ws.SelectedStrategies, + int(ws.BookingMode), request) if err != nil { return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err } @@ -77,7 +101,7 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest if ws.End != nil && ws.Start.Add(time.Duration(longest)*time.Second).After(*ws.End) { ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n" } - execs, err := ws.GetExecutions(wf) + execs, err := ws.GetExecutions(wf, isPreemptible) if err != nil { return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err } @@ -250,7 +274,7 @@ VERIFY THAT WE HANDLE DIFFERENCE BETWEEN LOCATION TIME && BOOKING * getExecutions is a function that returns the executions of a workflow * it returns an array of workflow_execution.WorkflowExecution */ -func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow) ([]*WorkflowExecution, error) { +func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow, isPreemptible bool) ([]*WorkflowExecution, error) { workflows_executions := []*WorkflowExecution{} dates, err := ws.GetDates() if err != nil { @@ -262,12 +286,19 @@ func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow) ([]*Workf UUID: uuid.New().String(), // set the uuid of the execution Name: workflow.Name + "_execution_" + date.Start.String(), // set the name of the execution }, + Priority: 1, ExecutionsID: ws.UUID, ExecDate: date.Start, // set the execution date EndDate: date.End, // set the end date State: enum.DRAFT, // set the state to 1 (scheduled) WorkflowID: workflow.GetID(), // set the workflow id dependancy of the execution } + if ws.BookingMode != booking.PLANNED { + obj.Priority = 0 + } + if ws.BookingMode == booking.PREEMPTED && isPreemptible { + obj.Priority = 7 + } workflows_executions = append(workflows_executions, obj) } return workflows_executions, nil diff --git a/models/workspace/workspace.go b/models/workspace/workspace.go index 330bb3e..53bafd5 100644 --- a/models/workspace/workspace.go +++ b/models/workspace/workspace.go @@ -20,13 +20,13 @@ func (d *Workspace) GetAccessor(request *tools.APIRequest) utils.Accessor { return NewAccessor(request) // Create a new instance of the accessor } -func (ao *Workspace) VerifyAuth(request *tools.APIRequest) bool { +func (ao *Workspace) VerifyAuth(callName string, request *tools.APIRequest) bool { if ao.Shared != "" { shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared) if code != 200 || shared == nil { return false } - return shared.VerifyAuth(request) + return shared.VerifyAuth(callName, request) } - return ao.AbstractObject.VerifyAuth(request) + return ao.AbstractObject.VerifyAuth(callName, request) } diff --git a/tools/api.go b/tools/api.go index 50623b1..ad31e4e 100644 --- a/tools/api.go +++ b/tools/api.go @@ -16,6 +16,7 @@ type APIRequest struct { PeerID string Groups []string Caller *HTTPCaller + Admin bool } /* diff --git a/tools/enums.go b/tools/enums.go index 58cfa54..06a314b 100644 --- a/tools/enums.go +++ b/tools/enums.go @@ -31,6 +31,7 @@ const ( BILL MINIO_SVCACC MINIO_SVCACC_SECRET + NATIVE_TOOL ) var NOAPI = "" @@ -77,6 +78,7 @@ var DefaultAPI = [...]string{ NOAPI, MINIO, MINIO, + CATALOGAPI, } // Bind the standard data name to the data type @@ -108,6 +110,7 @@ var Str = [...]string{ "bill", "service_account", "secret", + "native_tool", } func FromInt(i int) string { @@ -131,5 +134,5 @@ 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, - LIVE_DATACENTER, LIVE_STORAGE, BILL} + LIVE_DATACENTER, LIVE_STORAGE, BILL, NATIVE_TOOL} }