325 lines
12 KiB
Go
Executable File
325 lines
12 KiB
Go
Executable File
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{}
|
|
}
|