package resources import ( "errors" "slices" "time" "cloud.o-forge.io/core/oc-lib/config" "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" "github.com/biter777/countries" "github.com/marcinwyszynski/geopoint" ) // AbstractResource is the struct containing all of the attributes commons to all ressources // Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior // http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang /* * AbstractResource is a struct that represents a resource * it defines the resource data */ type abstractResource struct { utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) 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 ResourceModel *resource_model.ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"` // ResourceModel is the model of the resource UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"` } func (r *abstractResource) StoreDraftDefault() { r.IsDraft = true } func (r *AbstractCustomizedResource[T]) 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 } func (ao *abstractResource) GetAccessor(request *tools.APIRequest) utils.Accessor { return nil } func (ao *abstractResource) GetCreatorID() string { return ao.CreatorID } func (abs *abstractResource) SetResourceModel(model *resource_model.ResourceModel) { abs.ResourceModel = model } type AbstractResource[T InstanceITF] struct { abstractResource Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource } func (abs *AbstractResource[T]) SetAllowedInstances(request *tools.APIRequest) { abs.Instances = verifyAuthAction[T](abs.Instances, request) } func (abs *AbstractResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF { for _, instance := range abs.Instances { partners, grps := instance.GetPeerGroups() for i, p := range grps { if request == nil { continue } if _, ok := p[request.PeerID]; ok { return partners[i] } } } return nil } type AbstractCustomizedResource[T InstanceITF] struct { abstractResource 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"` SelectedInstance T `json:"selected_instance,omitempty" bson:"selected_instance,omitempty"` SelectedPricing string `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"` } func (abs *AbstractCustomizedResource[T]) SetStartUsage(start time.Time) { if abs.UsageStart == nil { abs.UsageStart = &start } } func (abs *AbstractCustomizedResource[T]) SetEndUsage(end time.Time) { if abs.UsageEnd == nil { abs.UsageEnd = &end } } func (abs *AbstractCustomizedResource[T]) IsBuying(request *tools.APIRequest) bool { return abs.GetPartnership(request).GetPricing(abs.SelectedPricing).IsBuying() } func (abs *AbstractCustomizedResource[T]) GetLocationEnd() *time.Time { return abs.UsageEnd } func (abs *AbstractCustomizedResource[T]) GetLocationStart() *time.Time { return abs.UsageStart } func (abs *AbstractCustomizedResource[T]) GetExplicitDurationInS() float64 { if abs.ExplicitBookingDurationS == 0 { if abs.UsageEnd == nil || abs.UsageStart == nil { return time.Duration(1 * time.Hour).Seconds() } return abs.UsageEnd.Sub(*abs.UsageStart).Seconds() } return abs.ExplicitBookingDurationS } func (abs *AbstractCustomizedResource[T]) GetPricingID() string { return abs.SelectedPricing } func (r *AbstractCustomizedResource[T]) GetPrice(request *tools.APIRequest) (float64, error) { if r.UsageStart == nil || r.UsageEnd == nil { return 0, errors.New("Usage start and end must be set") } partner := r.GetPartnership(request) if partner != nil && partner.GetPricing(r.SelectedPricing) != nil { return 0, errors.New("Pricing strategy not found") } return partner.GetPricing(r.SelectedPricing).GetPrice(1, 0, *r.UsageStart, *r.UsageEnd, request) } func (abs *AbstractCustomizedResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF { partners, grps := abs.SelectedInstance.GetPeerGroups() for i, p := range grps { if request == nil { continue } if _, ok := p[request.PeerID]; ok { return partners[i] } } return nil } func verifyAuthAction[T InstanceITF](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) } for _, grp := range grps { if slices.Contains(request.Groups, grp) { instances = append(instances, instance) } } } } } return instances } /* * VerifyPartnerships is a function that verifies the partnerships of the resource * an instance can only have one partnership available for a peer * it returns a boolean */ func (abs *AbstractResource[T]) VerifyPartnerships() bool { // a peer can be part of only one partnership by instance // may we need to define partnership in a different DB for _, instance := range abs.Instances { if !instance.VerifyPartnerships() { return false } } return true } func (d *AbstractResource[T]) Trim() { if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok { // TODO clean up the peer groups that are not in the allowed peers group for _, instance := range d.Instances { instance.ClearPeerGroups() } } } func (abs *AbstractResource[T]) VerifyAuth(request *tools.APIRequest) bool { return len(verifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request) } type ResourceInstance[T ResourcePartnerITF] struct { UUID string `json:"id,omitempty" bson:"id,omitempty"` Location geopoint.GeoPoint `json:"location,omitempty" bson:"location,omitempty"` Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"` // Url string `json:"url,omitempty" bson:"url,omitempty"` AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"` Partnerships []T `json:"partner_resource,omitempty" bson:"partner_resource,omitempty"` } func (ri *ResourceInstance[T]) GetID() string { return ri.UUID } func (r *ResourceInstance[T]) VerifyPartnerships() bool { peersMultiple := map[string]int{} for _, p := range r.Partnerships { for k, g := range p.GetPeerGroups() { for _, v := range g { if _, ok := peersMultiple[k+"_"+v]; !ok { peersMultiple[k+"_"+v] = 0 } peersMultiple[k+"_"+v]++ } } } for _, p := range peersMultiple { if p > 1 { return false } } return true } 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()) } 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[string]T `json:"pricing,omitempty" bson:"pricing,omitempty"` } func (rp *ResourcePartnerShip[T]) GetPricing(id string) pricing.PricingProfileITF { return rp.PricingProfiles[id] } func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string { return rp.PeerGroups } func (rp *ResourcePartnerShip[T]) ClearPeerGroups() { rp.PeerGroups = map[string][]string{} } /* -> when a workflow should book a resource -> it must be able to book a resource for a particular time -> the resource should be available for the time -> we must be able to parameter the resource for the time -> before bookin' */