Repository: iter8-tools/iter8
Pull request #1563 (2023-07-25 18:58:35)

weighted hash
Weight response of hash function to respect version weights.

Fixes https://github.com/iter8-tools/iter8/issues/1517


Patch:
diff --git a/abn/service_impl.go b/abn/service_impl.go
index e446e9cc..5691fe45 100644
--- a/abn/service_impl.go
+++ b/abn/service_impl.go
@@ -63,7 +63,7 @@ func lookupInternal(application string, user string) (controllers.RoutemapInterf
 // Inspired by https://github.com/tysonmote/rendezvous/blob/master/rendezvous.go
 func rendezvousGet(s controllers.RoutemapInterface, user string) int {
 	// current maximimum score as computed by the hash function
-	var maxScore uint64
+	var maxScore float64
 	// maxVersionNumber is the version index with the current maximum score
 	var maxVersionNumber int
 
@@ -73,12 +73,20 @@ func rendezvousGet(s controllers.RoutemapInterface, user string) int {
 	s.RLock()
 	defer s.RUnlock()
 
+	sumW := uint32(0)
+	for versionNumber := range s.GetVersions() {
+		sumW += s.Weights()[versionNumber]
+	}
+
 	for versionNumber, version := range s.GetVersions() {
-		if s.Weights()[versionNumber] == 0 {
+		w := s.Weights()[versionNumber]
+		if w == 0 {
 			continue
 		}
-		score := hash(fmt.Sprintf("%d", versionNumber), *version.GetSignature(), user)
-		log.Logger.Debugf("hash(%d,%s) --> %d  --  %d", versionNumber, user, score, maxScore)
+		wFactor := float64(w) / float64(sumW)
+		h := hash(fmt.Sprintf("%d", versionNumber), *version.GetSignature(), user)
+		score := wFactor * float64(h)
+		log.Logger.Debugf("hash(%d,%s) --> %f  --  %f", versionNumber, user, score, maxScore)
 		if score >= maxScore {
 			maxScore = score
 			maxVersionNumber = versionNumber


Test patch:
diff --git a/abn/service_impl_test.go b/abn/service_impl_test.go
index aaa07043..7824af83 100644
--- a/abn/service_impl_test.go
+++ b/abn/service_impl_test.go
@@ -4,6 +4,8 @@ import (
 	"testing"
 
 	"github.com/dgraph-io/badger/v4"
+	"github.com/google/uuid"
+	util "github.com/iter8-tools/iter8/base"
 	"github.com/iter8-tools/iter8/storage/badgerdb"
 	"github.com/stretchr/testify/assert"
 )
@@ -36,3 +38,55 @@ func TestLookupInternal(t *testing.T) {
 		assert.Equal(t, tr, versionNumbers[i])
 	}
 }
+
+func TestWeights(t *testing.T) {
+	var err error
+
+	// set up test metrics db for recording users
+	tempDirPath := t.TempDir()
+	MetricsClient, err = badgerdb.GetClient(badger.DefaultOptions(tempDirPath), badgerdb.AdditionalOptions{})
+	assert.NoError(t, err)
+
+	// setup: add desired routemaps to allRoutemaps
+	testRM := testRoutemaps{
+		allroutemaps: setupRoutemaps(t, *getWeightedTestRM("default", "test", []uint32{3, 1})),
+	}
+	allRoutemaps = &testRM
+
+	tries := 100
+	versionNumbers := make([]int, tries)
+	for i := 0; i < tries; i++ {
+		_, v, err := lookupInternal("default/test", uuid.NewString())
+		assert.NoError(t, err)
+		versionNumbers[i] = v
+	}
+
+	// expect 3/4 will be for version 0 (weight 3); ie, 75
+	// expect 1/4 will be for version 1 (weight 1); ie, 25
+	// compute number for version 1 by summing versionNumbers
+	// assert less than 30 (bigger than 25)
+	// there is a slight possibility of test failure
+
+	sum := 0
+	for i := 1; i < tries; i++ {
+		sum += versionNumbers[i]
+	}
+	assert.Less(t, sum, 30)
+}
+
+func getWeightedTestRM(namespace, name string, weights []uint32) *testroutemap {
+	copyWeights := make([]uint32, len(weights))
+	versions := make([]testversion, len(weights))
+	for i := range weights {
+		copyWeights[i] = weights[i]
+		versions[i] = testversion{signature: util.StringPointer(uuid.NewString())}
+	}
+
+	return &testroutemap{
+		namespace:         namespace,
+		name:              name,
+		versions:          versions,
+		normalizedWeights: copyWeights,
+	}
+
+}