Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions broker/adapter/api_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,11 @@ func (a *ApiDirectory) FilterAndSort(ctx common.ExtendedContext, entries []Suppl
Match: false,
})
}
priority := math.MaxInt
sharedPriority := math.MaxInt
for name, reqNet := range reqNetworks {
if _, ok := supNetworks[name]; ok {
if priority > reqNet.Priority {
priority = reqNet.Priority
if sharedPriority > reqNet.Priority {
sharedPriority = reqNet.Priority
}
for i, n := range supMatch.Networks {
if n.Name == name {
Expand All @@ -202,11 +202,12 @@ func (a *ApiDirectory) FilterAndSort(ctx common.ExtendedContext, entries []Suppl
}
return cmp.Compare(a.Name, b.Name)
})
if priority < math.MaxInt {
sup.Priority = priority
if sharedPriority < math.MaxInt {
sup.Priority = sharedPriority
suppTiers := getPeerTiers(sup.CustomData)
supMatch.Tiers = make([]TierMatch, 0, len(suppTiers))
cost := math.MaxFloat64
priority := math.MaxInt
for _, suppTier := range suppTiers {
var tierMatch TierMatch
tierMatch.Name = suppTier.Name
Expand All @@ -217,8 +218,10 @@ func (a *ApiDirectory) FilterAndSort(ctx common.ExtendedContext, entries []Suppl
suppTypeMatch := svcType == "" || svcType == strings.ToLower(suppTier.Type)
suppLevelMatch := svcLevel == "" || svcLevel == strings.ToLower(suppTier.Level)
suppCostMatch := costMatches(suppTier.Cost, maxCost)
tierPriority := sharedNetworkPriorityForCost(suppTier.Cost, reqNetworks, supNetworks)
suppNetworkMatch := tierPriority < math.MaxInt

if suppTypeMatch && suppLevelMatch && suppCostMatch {
if suppTypeMatch && suppLevelMatch && suppCostMatch && suppNetworkMatch {
reciprocal := true
Comment thread
jakub-id marked this conversation as resolved.
//supplier tier matched the request, if the tier is free it must be reciprocal
if suppTier.Cost == 0 {
Expand All @@ -234,8 +237,9 @@ func (a *ApiDirectory) FilterAndSort(ctx common.ExtendedContext, entries []Suppl
}
}
tierMatch.Match = reciprocal
if reciprocal && cost > suppTier.Cost {
if reciprocal && (cost > suppTier.Cost || cost == suppTier.Cost && priority > tierPriority) {
cost = suppTier.Cost
priority = tierPriority
}
}
supMatch.Tiers = append(supMatch.Tiers, tierMatch)
Expand All @@ -257,6 +261,7 @@ func (a *ApiDirectory) FilterAndSort(ctx common.ExtendedContext, entries []Suppl
supMatch.Match = true
supMatch.Cost = fmt.Sprintf("%.2f", cost)
sup.Cost = cost
sup.Priority = priority
filtered = append(filtered, sup)
}
supMatch.Priority = sup.Priority
Expand Down Expand Up @@ -289,6 +294,32 @@ func costMatches(suppCost, maxCost float64) bool {
}
}

func sharedNetworkPriorityForCost(suppCost float64, reqNetworks, supNetworks map[string]Network) int {
priority := math.MaxInt
for name, reqNet := range reqNetworks {
supNet, ok := supNetworks[name]
if !ok {
continue
}
if networkAllowsCost(reqNet, suppCost) && networkAllowsCost(supNet, suppCost) {
if priority > reqNet.Priority {
priority = reqNet.Priority
}
}
}
return priority
}

func networkAllowsCost(network Network, cost float64) bool {
if network.Reciprocal == nil {
return true
}
if *network.Reciprocal {
return cost == 0
}
return cost > 0
}

func CompareSuppliers(a, b SupplierOrdering) int {
if a.IsLocal() && !b.IsLocal() {
return -1
Expand Down Expand Up @@ -316,8 +347,9 @@ func getPeerNetworks(peerData directory.Entry) map[string]Network {
for _, n := range *peerData.Networks {
if n.Priority != nil {
networks[n.Name] = Network{
Name: n.Name,
Priority: int(*n.Priority),
Name: n.Name,
Priority: int(*n.Priority),
Reciprocal: n.Reciprocal,
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions broker/adapter/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ func (s Supplier) IsLocal() bool { return s.Local }
func (s Supplier) GetRatio() float32 { return s.Ratio }

type Network struct {
Name string `json:"name"`
Priority int `json:"priority"`
Name string `json:"name"`
Priority int `json:"priority"`
Reciprocal *bool `json:"reciprocal,omitempty"`
}

type Tier struct {
Expand Down
157 changes: 157 additions & 0 deletions broker/test/adapter/api_directory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ func createDirectoryAdapter(urls ...string) adapter.DirectoryLookupAdapter {
return adapter.CreateApiDirectory(http.DefaultClient, urls)
}

func boolPtr(v bool) *bool {
return &v
}

func intPtr(v int) *int {
return &v
}

func withNetworkReciprocal(entry directory.Entry, reciprocal *bool) directory.Entry {
if entry.Networks == nil {
return entry
}
networks := slices.Clone(*entry.Networks)
for i := range networks {
networks[i].Reciprocal = reciprocal
}
entry.Networks = &networks
return entry
}

func TestGetVendorFromUrl(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -495,6 +515,143 @@ func TestFilterAndSortReciprocal(t *testing.T) {
assert.Equal(t, "copy", rotaInfo.Request.Type)
}

func TestFilterAndSortReciprocalNetworkExcludesPaidTiers(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
ad := createDirectoryAdapter("")
requesterData := withNetworkReciprocal(dirEntries.Items[0], boolPtr(true))
entries := []adapter.Supplier{
{PeerId: "2", Ratio: 0.7, Symbol: "AU-NU", CustomData: dirEntries.Items[2]},
}
serviceInfo := iso18626.ServiceInfo{
ServiceLevel: &iso18626.TypeSchemeValuePair{
Text: "Core",
},
ServiceType: iso18626.TypeServiceTypeLoan,
}
billingInfo := iso18626.BillingInfo{
MaximumCosts: &iso18626.TypeCosts{
MonetaryValue: utils.XSDDecimal{
Base: 3500,
Exp: 2,
},
},
}

entries, rotaInfo := ad.FilterAndSort(appCtx, entries, requesterData, &serviceInfo, &billingInfo)

assert.Empty(t, entries)
assert.Len(t, rotaInfo.Suppliers, 1)
assert.False(t, rotaInfo.Suppliers[0].Match)
}

func TestFilterAndSortPaidNetworkExcludesFreeTiers(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
ad := createDirectoryAdapter("")
requesterData := withNetworkReciprocal(dirEntries.Items[4], boolPtr(false))
entries := []adapter.Supplier{
{PeerId: "3", Ratio: 0.7, Symbol: "AU-VVWA", CustomData: dirEntries.Items[4]},
}
serviceInfo := iso18626.ServiceInfo{
ServiceLevel: &iso18626.TypeSchemeValuePair{
Text: "Rush",
},
ServiceType: iso18626.TypeServiceTypeCopy,
}
billingInfo := iso18626.BillingInfo{
MaximumCosts: &iso18626.TypeCosts{
MonetaryValue: utils.XSDDecimal{
Base: 0,
Exp: 2,
},
},
}

entries, rotaInfo := ad.FilterAndSort(appCtx, entries, requesterData, &serviceInfo, &billingInfo)

assert.Empty(t, entries)
assert.Len(t, rotaInfo.Suppliers, 1)
assert.False(t, rotaInfo.Suppliers[0].Match)
}

func TestFilterAndSortPaidNetworkAllowsPaidTiers(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
ad := createDirectoryAdapter("")
requesterData := withNetworkReciprocal(dirEntries.Items[0], boolPtr(false))
entries := []adapter.Supplier{
{PeerId: "2", Ratio: 0.7, Symbol: "AU-NU", CustomData: withNetworkReciprocal(dirEntries.Items[2], boolPtr(false))},
}
serviceInfo := iso18626.ServiceInfo{
ServiceLevel: &iso18626.TypeSchemeValuePair{
Text: "Core",
},
ServiceType: iso18626.TypeServiceTypeLoan,
}
billingInfo := iso18626.BillingInfo{
MaximumCosts: &iso18626.TypeCosts{
MonetaryValue: utils.XSDDecimal{
Base: 3500,
Exp: 2,
},
},
}

entries, rotaInfo := ad.FilterAndSort(appCtx, entries, requesterData, &serviceInfo, &billingInfo)

assert.Len(t, entries, 1)
assert.Equal(t, "2", entries[0].PeerId)
assert.Equal(t, 34.4, entries[0].Cost)
assert.Len(t, rotaInfo.Suppliers, 1)
assert.True(t, rotaInfo.Suppliers[0].Match)
assert.Equal(t, "34.40", rotaInfo.Suppliers[0].Cost)
}

func TestFilterAndSortUsesCompatibleNetworkPriority(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
ad := createDirectoryAdapter("")
requesterNetworks := []directory.Network{
{Name: "Reciprocal", Priority: intPtr(1), Reciprocal: boolPtr(true)},
{Name: "Paid Low", Priority: intPtr(5), Reciprocal: boolPtr(false)},
{Name: "Paid High", Priority: intPtr(3), Reciprocal: boolPtr(false)},
}
paidTier := []directory.Tier{
{Name: "Paid Core Loan", Level: "Core", Type: "Loan", Cost: 34.4},
}
requesterData := directory.Entry{Name: "Requester", Networks: &requesterNetworks}
supplierANetworks := []directory.Network{
{Name: "Reciprocal", Priority: intPtr(1), Reciprocal: boolPtr(true)},
{Name: "Paid Low", Priority: intPtr(5), Reciprocal: boolPtr(false)},
}
supplierBNetworks := []directory.Network{
{Name: "Paid High", Priority: intPtr(3), Reciprocal: boolPtr(false)},
}
entries := []adapter.Supplier{
{PeerId: "A", Symbol: "A", CustomData: directory.Entry{Name: "Supplier A", Networks: &supplierANetworks, Tiers: &paidTier}},
{PeerId: "B", Symbol: "B", CustomData: directory.Entry{Name: "Supplier B", Networks: &supplierBNetworks, Tiers: &paidTier}},
}
serviceInfo := iso18626.ServiceInfo{
ServiceLevel: &iso18626.TypeSchemeValuePair{
Text: "Core",
},
ServiceType: iso18626.TypeServiceTypeLoan,
}
billingInfo := iso18626.BillingInfo{
MaximumCosts: &iso18626.TypeCosts{
MonetaryValue: utils.XSDDecimal{
Base: 3500,
Exp: 2,
},
},
}

entries, _ = ad.FilterAndSort(appCtx, entries, requesterData, &serviceInfo, &billingInfo)

assert.Len(t, entries, 2)
assert.Equal(t, "B", entries[0].PeerId)
assert.Equal(t, 3, entries[0].Priority)
assert.Equal(t, "A", entries[1].PeerId)
assert.Equal(t, 5, entries[1].Priority)
}

func TestFilterAndSortNoFilters(t *testing.T) {
appCtx := common.CreateExtCtxWithArgs(context.Background(), nil)
ad := createDirectoryAdapter("")
Expand Down
2 changes: 2 additions & 0 deletions directory/directory_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ components:
type: string
priority:
type: integer
reciprocal:
type: boolean
Membership:
type: object
required:
Expand Down
Loading