@ -15,53 +15,57 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/scheduler/limits"
)
var noQueueLimits = limits . NewQueueLimits ( nil )
func TestQueues ( t * testing . T ) {
uq := newTenantQueues ( 0 , 0 )
uq := newTenantQueues ( 0 , 0 , noQueueLimits )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
uq . addConsumerToConnection ( "queri er-1" )
uq . addConsumerToConnection ( "queri er-2" )
uq . addConsumerToConnection ( "consum er-1" )
uq . addConsumerToConnection ( "consum er-2" )
q , u , lastUserIndex := uq . getNextQueueForConsumer ( - 1 , "queri er-1" )
q , u , lastUserIndex := uq . getNextQueueForConsumer ( - 1 , "consum er-1" )
assert . Nil ( t , q )
assert . Equal ( t , "" , u )
// Add queues: [one]
qOne := getOrAdd ( t , uq , "one" , 0 )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qOne , qOne )
qOne := getOrAdd ( t , uq , "one" )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qOne , qOne )
// [one two]
qTwo := getOrAdd ( t , uq , "two" , 0 )
qTwo := getOrAdd ( t , uq , "two" )
assert . NotEqual ( t , qOne , qTwo )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qTwo , qOne , qTwo , qOne )
confirmOrderForQueri er ( t , uq , "queri er-2" , - 1 , qOne , qTwo , qOne )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qTwo , qOne , qTwo , qOne )
confirmOrderForConsum er ( t , uq , "consum er-2" , - 1 , qOne , qTwo , qOne )
// [one two three]
// confirm fifo by adding a third queue and iterating to it
qThree := getOrAdd ( t , uq , "three" , 0 )
qThree := getOrAdd ( t , uq , "three" )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qTwo , qThree , qOne )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qTwo , qThree , qOne )
// Remove one: ["" two three]
uq . deleteQueue ( "one" )
assert . NoError ( t , isConsistent ( uq ) )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qTwo , qThree , qTwo )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qTwo , qThree , qTwo )
// "four" is added at the beginning of the list: [four two three]
qFour := getOrAdd ( t , uq , "four" , 0 )
qFour := getOrAdd ( t , uq , "four" )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qThree , qFour , qTwo , qThree )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qThree , qFour , qTwo , qThree )
// Remove two: [four "" three]
uq . deleteQueue ( "two" )
assert . NoError ( t , isConsistent ( uq ) )
lastUserIndex = confirmOrderForQueri er ( t , uq , "queri er-1" , lastUserIndex , qFour , qThree , qFour )
lastUserIndex = confirmOrderForConsum er ( t , uq , "consum er-1" , lastUserIndex , qFour , qThree , qFour )
// Remove three: [four]
uq . deleteQueue ( "three" )
@ -71,55 +75,55 @@ func TestQueues(t *testing.T) {
uq . deleteQueue ( "four" )
assert . NoError ( t , isConsistent ( uq ) )
q , _ , _ = uq . getNextQueueForConsumer ( lastUserIndex , "queri er-1" )
q , _ , _ = uq . getNextQueueForConsumer ( lastUserIndex , "consum er-1" )
assert . Nil ( t , q )
}
func TestQueuesOnTerminatingQueri er ( t * testing . T ) {
uq := newTenantQueues ( 0 , 0 )
func TestQueuesOnTerminatingConsum er ( t * testing . T ) {
uq := newTenantQueues ( 0 , 0 , noQueueLimits )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
uq . addConsumerToConnection ( "queri er-1" )
uq . addConsumerToConnection ( "queri er-2" )
uq . addConsumerToConnection ( "consum er-1" )
uq . addConsumerToConnection ( "consum er-2" )
// Add queues: [one, two]
qOne := getOrAdd ( t , uq , "one" , 0 )
qTwo := getOrAdd ( t , uq , "two" , 0 )
confirmOrderForQueri er ( t , uq , "queri er-1" , - 1 , qOne , qTwo , qOne , qTwo )
confirmOrderForQueri er ( t , uq , "queri er-2" , - 1 , qOne , qTwo , qOne , qTwo )
// After notify shutdown for queri er-2, it's expected to own no queue.
uq . notifyQuerierShutdown ( "queri er-2" )
q , u , _ := uq . getNextQueueForConsumer ( - 1 , "queri er-2" )
qOne := getOrAdd ( t , uq , "one" )
qTwo := getOrAdd ( t , uq , "two" )
confirmOrderForConsum er ( t , uq , "consum er-1" , - 1 , qOne , qTwo , qOne , qTwo )
confirmOrderForConsum er ( t , uq , "consum er-2" , - 1 , qOne , qTwo , qOne , qTwo )
// After notify shutdown for consum er-2, it's expected to own no queue.
uq . notifyQuerierShutdown ( "consum er-2" )
q , u , _ := uq . getNextQueueForConsumer ( - 1 , "consum er-2" )
assert . Nil ( t , q )
assert . Equal ( t , "" , u )
// However, queri er-1 still get queues because it's still running.
confirmOrderForQueri er ( t , uq , "queri er-1" , - 1 , qOne , qTwo , qOne , qTwo )
// However, consum er-1 still get queues because it's still running.
confirmOrderForConsum er ( t , uq , "consum er-1" , - 1 , qOne , qTwo , qOne , qTwo )
// After disconnecting queri er-2, it's expected to own no queue.
uq . removeConsumer ( "queri er-2" )
q , u , _ = uq . getNextQueueForConsumer ( - 1 , "queri er-2" )
// After disconnecting consum er-2, it's expected to own no queue.
uq . removeConsumer ( "consum er-2" )
q , u , _ = uq . getNextQueueForConsumer ( - 1 , "consum er-2" )
assert . Nil ( t , q )
assert . Equal ( t , "" , u )
}
func TestQueuesWithQueriers ( t * testing . T ) {
uq := newTenantQueues ( 0 , 0 )
func TestQueuesWithConsumers ( t * testing . T ) {
maxConsumers := 5
uq := newTenantQueues ( 0 , 0 , & mockQueueLimits { maxConsumers : maxConsumers } )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
queri ers := 30
consum ers := 30
users := 1000
maxQueriersPerUser := 5
// Add some queri ers.
for ix := 0 ; ix < queri ers; ix ++ {
qid := fmt . Sprintf ( "queri er-%d" , ix )
// Add some consum ers.
for ix := 0 ; ix < consum ers; ix ++ {
qid := fmt . Sprintf ( "consum er-%d" , ix )
uq . addConsumerToConnection ( qid )
// No queri er has any queues yet.
// No consum er has any queues yet.
q , u , _ := uq . getNextQueueForConsumer ( - 1 , qid )
assert . Nil ( t , q )
assert . Equal ( t , "" , u )
@ -130,19 +134,19 @@ func TestQueuesWithQueriers(t *testing.T) {
// Add user queues.
for u := 0 ; u < users ; u ++ {
uid := fmt . Sprintf ( "user-%d" , u )
getOrAdd ( t , uq , uid , maxQueriersPerUser )
getOrAdd ( t , uq , uid )
// Verify it has maxQueriersPerUser queri ers assigned now.
// Verify it has maxConsumers consum ers assigned now.
qs := uq . mapping . GetByKey ( uid ) . consumers
assert . Equal ( t , maxQueriersPerUser , len ( qs ) )
assert . Equal ( t , maxConsumers , len ( qs ) )
}
// After adding all users, verify results. For each queri er, find out how many different users it handles,
// After adding all users, verify results. For each consum er, find out how many different users it handles,
// and compute mean and stdDev.
queriers Map := make ( map [ string ] int )
consumer Map := make ( map [ string ] int )
for q := 0 ; q < queri ers; q ++ {
qid := fmt . Sprintf ( "queri er-%d" , q )
for q := 0 ; q < consum ers; q ++ {
qid := fmt . Sprintf ( "consum er-%d" , q )
lastUserIndex := StartIndex
for {
@ -151,25 +155,25 @@ func TestQueuesWithQueriers(t *testing.T) {
break
}
lastUserIndex = newIx
queriers Map[ qid ] ++
consumer Map[ qid ] ++
}
}
mean := float64 ( 0 )
for _ , c := range queriers Map {
for _ , c := range consumer Map {
mean += float64 ( c )
}
mean = mean / float64 ( len ( queriers Map) )
mean = mean / float64 ( len ( consumer Map) )
stdDev := float64 ( 0 )
for _ , c := range queriers Map {
for _ , c := range consumer Map {
d := float64 ( c ) - mean
stdDev += ( d * d )
}
stdDev = math . Sqrt ( stdDev / float64 ( len ( queriers Map) ) )
stdDev = math . Sqrt ( stdDev / float64 ( len ( consumer Map) ) )
t . Log ( "mean:" , mean , "stddev:" , stdDev )
assert . InDelta ( t , users * maxQueriersPerUser / queri ers , mean , 1 )
assert . InDelta ( t , users * maxConsumers / consum ers , mean , 1 )
assert . InDelta ( t , stdDev , 0 , mean * 0.2 )
}
@ -183,7 +187,7 @@ func TestQueuesConsistency(t *testing.T) {
for testName , testData := range tests {
t . Run ( testName , func ( t * testing . T ) {
uq := newTenantQueues ( 0 , testData . forgetDelay )
uq := newTenantQueues ( 0 , testData . forgetDelay , & mockQueueLimits { maxConsumers : 3 } )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
@ -196,25 +200,27 @@ func TestQueuesConsistency(t *testing.T) {
for i := 0 ; i < 10000 ; i ++ {
switch r . Int ( ) % 6 {
case 0 :
assert . NotNil ( t , uq . getOrAddQueue ( generateTenant ( r ) , generateActor ( r ) , 3 ) )
q , err := uq . getOrAddQueue ( generateTenant ( r ) , generateActor ( r ) )
assert . NoError ( t , err )
assert . NotNil ( t , q )
case 1 :
qid := generateQuerier ( r )
qid := generateConsum er ( r )
_ , _ , luid := uq . getNextQueueForConsumer ( lastUserIndexes [ qid ] , qid )
lastUserIndexes [ qid ] = luid
case 2 :
uq . deleteQueue ( generateTenant ( r ) )
case 3 :
q := generateQueri er ( r )
q := generateConsum er ( r )
uq . addConsumerToConnection ( q )
conns [ q ] ++
case 4 :
q := generateQueri er ( r )
q := generateConsum er ( r )
if conns [ q ] > 0 {
uq . removeConsumerConnection ( q , time . Now ( ) )
conns [ q ] --
}
case 5 :
q := generateQueri er ( r )
q := generateConsum er ( r )
uq . notifyQuerierShutdown ( q )
}
@ -226,166 +232,166 @@ func TestQueuesConsistency(t *testing.T) {
func TestQueues_ForgetDelay ( t * testing . T ) {
const (
forgetDelay = time . Minute
maxQueriersPerUser = 1
numUsers = 100
forgetDelay = time . Minute
maxConsumers = 1
numUsers = 100
)
now := time . Now ( )
uq := newTenantQueues ( 0 , forgetDelay )
uq := newTenantQueues ( 0 , forgetDelay , & mockQueueLimits { maxConsumers : maxConsumers } )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
// 3 queri ers open 2 connections each.
// 3 consum ers open 2 connections each.
for i := 1 ; i <= 3 ; i ++ {
uq . addConsumerToConnection ( fmt . Sprintf ( "queri er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "queri er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "consum er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "consum er-%d" , i ) )
}
// Add user queues.
for i := 0 ; i < numUsers ; i ++ {
userID := fmt . Sprintf ( "user-%d" , i )
getOrAdd ( t , uq , userID , maxQueriersPerUser )
getOrAdd ( t , uq , userID )
}
// We expect queri er-1 to have some users.
querier1Users := getUsersByQueri er( uq , "queri er-1" )
require . NotEmpty ( t , queri er1Users)
// We expect consum er-1 to have some users.
consumer1Users := getUsersByConsum er( uq , "consum er-1" )
require . NotEmpty ( t , consum er1Users)
// Gracefully shutdown queri er-1.
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 20 * time . Second ) )
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 21 * time . Second ) )
uq . notifyQuerierShutdown ( "queri er-1" )
// Gracefully shutdown consum er-1.
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 20 * time . Second ) )
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 21 * time . Second ) )
uq . notifyQuerierShutdown ( "consum er-1" )
// We expect queri er-1 has been removed.
assert . NotContains ( t , uq . consumers , "queri er-1" )
// We expect consum er-1 has been removed.
assert . NotContains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
// We expect querier-1 users have been shuffled to other queri ers.
for _ , userID := range queri er1Users {
assert . Contains ( t , append ( getUsersByQueri er ( uq , "querier-2" ) , getUsersByQueri er ( uq , "queri er-3" ) ... ) , userID )
// We expect consumer-1 users have been shuffled to other consum ers.
for _ , userID := range consum er1Users {
assert . Contains ( t , append ( getUsersByConsum er ( uq , "consumer-2" ) , getUsersByConsum er ( uq , "consum er-3" ) ... ) , userID )
}
// Queri er-1 reconnects.
uq . addConsumerToConnection ( "queri er-1" )
uq . addConsumerToConnection ( "queri er-1" )
// Consum er-1 reconnects.
uq . addConsumerToConnection ( "consum er-1" )
uq . addConsumerToConnection ( "consum er-1" )
// We expect the initial querier-1 users have got back to queri er-1.
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
// We expect the initial consumer-1 users have got back to consum er-1.
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
// Queri er-1 abruptly terminates (no shutdown notification received).
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 40 * time . Second ) )
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 41 * time . Second ) )
// Consum er-1 abruptly terminates (no shutdown notification received).
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 40 * time . Second ) )
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 41 * time . Second ) )
// We expect queri er-1 has NOT been removed.
assert . Contains ( t , uq . consumers , "queri er-1" )
// We expect consum er-1 has NOT been removed.
assert . Contains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
// We expect the querier-1 users have not been shuffled to other queri ers.
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
// We expect the consumer-1 users have not been shuffled to other consum ers.
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
// Try to forget disconnected queriers, but queri er-1 forget delay hasn't passed yet.
// Try to forget disconnected consumers, but consum er-1 forget delay hasn't passed yet.
uq . forgetDisconnectedConsumers ( now . Add ( 90 * time . Second ) )
assert . Contains ( t , uq . consumers , "queri er-1" )
assert . Contains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
// Try to forget disconnected queriers. This time queri er-1 forget delay has passed.
// Try to forget disconnected consumers. This time consum er-1 forget delay has passed.
uq . forgetDisconnectedConsumers ( now . Add ( 105 * time . Second ) )
assert . NotContains ( t , uq . consumers , "queri er-1" )
assert . NotContains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
// We expect querier-1 users have been shuffled to other queri ers.
for _ , userID := range queri er1Users {
assert . Contains ( t , append ( getUsersByQueri er ( uq , "querier-2" ) , getUsersByQueri er ( uq , "queri er-3" ) ... ) , userID )
// We expect consumer-1 users have been shuffled to other consum ers.
for _ , userID := range consum er1Users {
assert . Contains ( t , append ( getUsersByConsum er ( uq , "consumer-2" ) , getUsersByConsum er ( uq , "consum er-3" ) ... ) , userID )
}
}
func TestQueues_ForgetDelay_ShouldCorrectlyHandleQueri erReconnectingBeforeForgetDelayIsPassed ( t * testing . T ) {
func TestQueues_ForgetDelay_ShouldCorrectlyHandleConsum erReconnectingBeforeForgetDelayIsPassed ( t * testing . T ) {
const (
forgetDelay = time . Minute
maxQueriersPerUser = 1
numUsers = 100
forgetDelay = time . Minute
maxConsumers = 1
numUsers = 100
)
now := time . Now ( )
uq := newTenantQueues ( 0 , forgetDelay )
uq := newTenantQueues ( 0 , forgetDelay , & mockQueueLimits { maxConsumers : maxConsumers } )
assert . NotNil ( t , uq )
assert . NoError ( t , isConsistent ( uq ) )
// 3 queri ers open 2 connections each.
// 3 consum ers open 2 connections each.
for i := 1 ; i <= 3 ; i ++ {
uq . addConsumerToConnection ( fmt . Sprintf ( "queri er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "queri er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "consum er-%d" , i ) )
uq . addConsumerToConnection ( fmt . Sprintf ( "consum er-%d" , i ) )
}
// Add user queues.
for i := 0 ; i < numUsers ; i ++ {
userID := fmt . Sprintf ( "user-%d" , i )
getOrAdd ( t , uq , userID , maxQueriersPerUser )
getOrAdd ( t , uq , userID )
}
// We expect queri er-1 to have some users.
querier1Users := getUsersByQueri er( uq , "queri er-1" )
require . NotEmpty ( t , queri er1Users)
// We expect consum er-1 to have some users.
consumer1Users := getUsersByConsum er( uq , "consum er-1" )
require . NotEmpty ( t , consum er1Users)
// Queri er-1 abruptly terminates (no shutdown notification received).
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 40 * time . Second ) )
uq . removeConsumerConnection ( "queri er-1" , now . Add ( 41 * time . Second ) )
// Consum er-1 abruptly terminates (no shutdown notification received).
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 40 * time . Second ) )
uq . removeConsumerConnection ( "consum er-1" , now . Add ( 41 * time . Second ) )
// We expect queri er-1 has NOT been removed.
assert . Contains ( t , uq . consumers , "queri er-1" )
// We expect consum er-1 has NOT been removed.
assert . Contains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
// We expect the querier-1 users have not been shuffled to other queri ers.
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
// We expect the consumer-1 users have not been shuffled to other consum ers.
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
// Try to forget disconnected queriers, but queri er-1 forget delay hasn't passed yet.
// Try to forget disconnected consumers, but consum er-1 forget delay hasn't passed yet.
uq . forgetDisconnectedConsumers ( now . Add ( 90 * time . Second ) )
// Queri er-1 reconnects.
uq . addConsumerToConnection ( "queri er-1" )
uq . addConsumerToConnection ( "queri er-1" )
// Consum er-1 reconnects.
uq . addConsumerToConnection ( "consum er-1" )
uq . addConsumerToConnection ( "consum er-1" )
assert . Contains ( t , uq . consumers , "queri er-1" )
assert . Contains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
// We expect the querier-1 users have not been shuffled to other queri ers.
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
// We expect the consumer-1 users have not been shuffled to other consum ers.
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
// Try to forget disconnected queriers far in the future, but there's no disconnected queri er.
// Try to forget disconnected consumers far in the future, but there's no disconnected consum er.
uq . forgetDisconnectedConsumers ( now . Add ( 200 * time . Second ) )
assert . Contains ( t , uq . consumers , "queri er-1" )
assert . Contains ( t , uq . consumers , "consum er-1" )
assert . NoError ( t , isConsistent ( uq ) )
for _ , userID := range queri er1Users {
assert . Contains ( t , getUsersByQueri er ( uq , "queri er-1" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-2" ) , userID )
assert . NotContains ( t , getUsersByQueri er ( uq , "queri er-3" ) , userID )
for _ , userID := range consum er1Users {
assert . Contains ( t , getUsersByConsum er ( uq , "consum er-1" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-2" ) , userID )
assert . NotContains ( t , getUsersByConsum er ( uq , "consum er-3" ) , userID )
}
}
@ -397,24 +403,27 @@ func generateTenant(r *rand.Rand) string {
return fmt . Sprint ( "tenant-" , r . Int ( ) % 5 )
}
func generateQueri er ( r * rand . Rand ) string {
return fmt . Sprint ( "queri er-" , r . Int ( ) % 5 )
func generateConsum er ( r * rand . Rand ) string {
return fmt . Sprint ( "consum er-" , r . Int ( ) % 5 )
}
func getOrAdd ( t * testing . T , uq * tenantQueues , tenant string , maxQueriers int ) Queue {
func getOrAdd ( t * testing . T , uq * tenantQueues , tenant string ) Queue {
actor := [ ] string { }
q := uq . getOrAddQueue ( tenant , actor , maxQueriers )
q , err := uq . getOrAddQueue ( tenant , actor )
assert . NoError ( t , err )
assert . NotNil ( t , q )
assert . NoError ( t , isConsistent ( uq ) )
assert . Equal ( t , q , uq . getOrAddQueue ( tenant , actor , maxQueriers ) )
q2 , err := uq . getOrAddQueue ( tenant , actor )
assert . NoError ( t , err )
assert . Equal ( t , q , q2 )
return q
}
func confirmOrderForQueri er ( t * testing . T , uq * tenantQueues , queri er string , lastUserIndex QueueIndex , qs ... Queue ) QueueIndex {
func confirmOrderForConsum er ( t * testing . T , uq * tenantQueues , consum er string , lastUserIndex QueueIndex , qs ... Queue ) QueueIndex {
t . Helper ( )
var n Queue
for _ , q := range qs {
n , _ , lastUserIndex = uq . getNextQueueForConsumer ( lastUserIndex , queri er)
n , _ , lastUserIndex = uq . getNextQueueForConsumer ( lastUserIndex , consum er)
assert . Equal ( t , q , n )
assert . NoError ( t , isConsistent ( uq ) )
}
@ -423,7 +432,7 @@ func confirmOrderForQuerier(t *testing.T, uq *tenantQueues, querier string, last
func isConsistent ( uq * tenantQueues ) error {
if len ( uq . sortedConsumers ) != len ( uq . consumers ) {
return fmt . Errorf ( "inconsistent number of sorted queriers and queri er connections" )
return fmt . Errorf ( "inconsistent number of sorted consumers and consum er connections" )
}
uc := 0
@ -441,16 +450,17 @@ func isConsistent(uq *tenantQueues) error {
uc ++
if q . maxQueriers == 0 && q . consumers != nil {
return fmt . Errorf ( "user %s has queriers, but maxQueriers=0" , u )
maxConsumers := uq . limits . MaxConsumers ( u , len ( uq . consumers ) )
if maxConsumers == 0 && q . consumers != nil {
return fmt . Errorf ( "consumers for user %s should be nil when no limits are set (when MaxConsumers is 0)" , u )
}
if q . maxQueri ers > 0 && len ( uq . sortedConsumers ) <= q . maxQueri ers && q . consumers != nil {
return fmt . Errorf ( "user %s has queriers set despite not enough queriers available " , u )
if maxConsum ers > 0 && len ( uq . sortedConsumers ) <= maxConsum ers && q . consumers != nil {
return fmt . Errorf ( "consumers for user %s should be nil when MaxConsumers allowed is higher than the available consumers " , u )
}
if q . maxQueri ers > 0 && len ( uq . sortedConsumers ) > q . maxQueri ers && len ( q . consumers ) != q . maxQueri ers {
return fmt . Errorf ( "user %s has incorrect number of queri ers, expected=%d, got=%d" , u , len ( q . consumers ) , q . maxQueriers )
if maxConsum ers > 0 && len ( uq . sortedConsumers ) > maxConsum ers && len ( q . consumers ) != maxConsum ers {
return fmt . Errorf ( "user %s has incorrect number of consum ers, expected=%d, got=%d" , u , maxConsumers , len ( q . consumers ) )
}
}
@ -461,67 +471,75 @@ func isConsistent(uq *tenantQueues) error {
return nil
}
// getUsersByQuerier returns the list of users handled by the provided queri erID.
func getUsersByQueri er ( queues * tenantQueues , queri erID string ) [ ] string {
// getUsersByConsumer returns the list of users handled by the provided consum erID.
func getUsersByConsum er ( queues * tenantQueues , consum erID string ) [ ] string {
var userIDs [ ] string
for _ , userID := range queues . mapping . Keys ( ) {
q := queues . mapping . GetByKey ( userID )
if q . consumers == nil {
// If it's nil then all queri ers can handle this user.
// If it's nil then all consum ers can handle this user.
userIDs = append ( userIDs , userID )
continue
}
if _ , ok := q . consumers [ queri erID] ; ok {
if _ , ok := q . consumers [ consum erID] ; ok {
userIDs = append ( userIDs , userID )
}
}
return userIDs
}
func TestShuffleQueri ers ( t * testing . T ) {
allQueri ers := [ ] string { "a" , "b" , "c" , "d" , "e" }
func TestShuffleConsum ers ( t * testing . T ) {
allConsum ers := [ ] string { "a" , "b" , "c" , "d" , "e" }
require . Nil ( t , shuffleConsumersForTenants ( 12345 , 10 , allQueri ers , nil ) )
require . Nil ( t , shuffleConsumersForTenants ( 12345 , len ( allQueriers ) , allQueri ers , nil ) )
require . Nil ( t , shuffleConsumersForTenants ( 12345 , 10 , allConsum ers , nil ) )
require . Nil ( t , shuffleConsumersForTenants ( 12345 , len ( allConsumers ) , allConsum ers , nil ) )
r1 := shuffleConsumersForTenants ( 12345 , 3 , allQueri ers , nil )
r1 := shuffleConsumersForTenants ( 12345 , 3 , allConsum ers , nil )
require . Equal ( t , 3 , len ( r1 ) )
// Same input produces same output.
r2 := shuffleConsumersForTenants ( 12345 , 3 , allQueri ers , nil )
r2 := shuffleConsumersForTenants ( 12345 , 3 , allConsum ers , nil )
require . Equal ( t , 3 , len ( r2 ) )
require . Equal ( t , r1 , r2 )
}
func TestShuffleQueri ersCorrectness ( t * testing . T ) {
const queri ersCount = 100
func TestShuffleConsum ersCorrectness ( t * testing . T ) {
const consum ersCount = 100
var allSortedQueri ers [ ] string
for i := 0 ; i < queri ersCount; i ++ {
allSortedQueri ers = append ( allSortedQueri ers , fmt . Sprintf ( "%d" , i ) )
var allSortedConsum ers [ ] string
for i := 0 ; i < consum ersCount; i ++ {
allSortedConsum ers = append ( allSortedConsum ers , fmt . Sprintf ( "%d" , i ) )
}
sort . Strings ( allSortedQueri ers )
sort . Strings ( allSortedConsum ers )
r := rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
const tests = 1000
for i := 0 ; i < tests ; i ++ {
toSelect := r . Intn ( queri ersCount)
toSelect := r . Intn ( consum ersCount)
if toSelect == 0 {
toSelect = 3
}
selected := shuffleConsumersForTenants ( r . Int63 ( ) , toSelect , allSortedQueri ers , nil )
selected := shuffleConsumersForTenants ( r . Int63 ( ) , toSelect , allSortedConsum ers , nil )
require . Equal ( t , toSelect , len ( selected ) )
sort . Strings ( allSortedQueri ers )
prevQueri er := ""
for _ , q := range allSortedQueri ers {
require . True ( t , prevQueri er < q , "non-unique queri er" )
prevQueri er = q
sort . Strings ( allSortedConsum ers )
prevConsum er := ""
for _ , q := range allSortedConsum ers {
require . True ( t , prevConsum er < q , "non-unique consum er" )
prevConsum er = q
ix := sort . SearchStrings ( allSortedQueri ers , q )
require . True ( t , ix < len ( allSortedQueriers ) && allSortedQueri ers [ ix ] == q , "selected querier is not between all queri ers" )
ix := sort . SearchStrings ( allSortedConsum ers , q )
require . True ( t , ix < len ( allSortedConsumers ) && allSortedConsum ers [ ix ] == q , "selected consumer is not between all consum ers" )
}
}
}
type mockQueueLimits struct {
maxConsumers int
}
func ( l * mockQueueLimits ) MaxConsumers ( _ string , _ int ) int {
return l . maxConsumers
}