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" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" "github.com/biter777/countries" "github.com/google/uuid" ) // 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"` AllowedBookingModes map[booking.BookingMode]*pricing.PricingVariation `bson:"allowed_booking_modes" json:"allowed_booking_modes"` } 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 } func (r *AbstractResource) GetType() string { return tools.INVALID.String() } func (r *AbstractResource) StoreDraftDefault() { r.IsDraft = true } func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { if r.IsDraft != set.IsDrafted() && set.IsDrafted() { return true, set // only state can be updated } return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set } func (r *AbstractResource) CanDelete() bool { return r.IsDraft // only draft bookings can be deleted } type AbstractInstanciatedResource[T ResourceInstanceITF] struct { AbstractResource // AbstractResource contains the basic fields of an object (id, name) Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource } func (abs *AbstractInstanciatedResource[T]) AddInstances(instance ResourceInstanceITF) { abs.Instances = append(abs.Instances, instance.(T)) } 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 { // 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(selectedInstance); t != nil { profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy) } 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 { for _, instance := range abs.Instances { instance.ClearEnv() } return abs } 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] } return nil } func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) { if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" { return } abs.Instances = VerifyAuthAction[T](abs.Instances, request) } func (d *AbstractInstanciatedResource[T]) Trim() { d.Type = d.GetType() if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok { for _, instance := range d.Instances { instance.ClearPeerGroups() } } } 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 { instances := []T{} for _, instance := range baseInstance { _, peerGroups := instance.GetPeerGroups() for _, peers := range peerGroups { if request == nil { continue } if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist { if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) { instances = append(instances, instance) continue } for _, grp := range grps { if slices.Contains(request.Groups, grp) { instances = append(instances, instance) } } } } } return instances } type GeoPoint struct { Latitude float64 `json:"latitude,omitempty" bson:"latitude,omitempty"` Longitude float64 `json:"longitude,omitempty" bson:"longitude,omitempty"` } 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"` } // TODO should kicks all selection func NewInstance[T ResourcePartnerITF](name string) *ResourceInstance[T] { return &ResourceInstance[T]{ AbstractObject: utils.AbstractObject{ UUID: uuid.New().String(), Name: name, }, Partnerships: []T{}, } } func (ri *ResourceInstance[T]) ClearEnv() { ri.Env = []models.Param{} ri.Inputs = []models.Param{} ri.Outputs = []models.Param{} } 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]) 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 } func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) { groups := []map[string][]string{} partners := []ResourcePartnerITF{} for _, p := range ri.Partnerships { 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 } func (ri *ResourceInstance[T]) ClearPeerGroups() { for _, p := range ri.Partnerships { p.ClearPeerGroups() } } type ResourcePartnerShip[T pricing.PricingProfileITF] struct { Namespace string `json:"namespace" bson:"namespace" default:"default-namespace"` PeerGroups map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"` PricingProfiles map[int]map[int]T `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"` // to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy } 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 } /* Le pricing doit être selectionné lors d'un scheduling... le type de paiement défini le type de stratégie de paiement note : il faut rajouté - une notion de facturation Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat. Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc. */ func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF { profiles := []pricing.PricingProfileITF{} if ri.PeerGroups[peerID] == nil { return profiles } for _, p := range ri.PeerGroups[peerID] { if slices.Contains(groups, p) || slices.Contains(groups, "*") { for _, ri := range ri.PricingProfiles { for _, i := range ri { profiles = append(profiles, i) } } 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 } func (rp *ResourcePartnerShip[T]) ClearPeerGroups() { rp.PeerGroups = map[string][]string{} }