mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-27 14:39:40 -08:00
162 lines
5 KiB
Go
162 lines
5 KiB
Go
|
// Copyright 2024 The Prometheus Authors
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package remote
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"math/rand"
|
||
|
"net"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/prometheus/common/config"
|
||
|
"github.com/stretchr/testify/mock"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
testNetwork = "tcp"
|
||
|
testAddrWithoutPort = "this-is-my-addr.without-port"
|
||
|
testAddrWithPort = "this-is-my-addr.without-port:123"
|
||
|
testPort = "123"
|
||
|
ip1 = "1.2.3.4"
|
||
|
ip2 = "5.6.7.8"
|
||
|
ip3 = "9.0.1.2"
|
||
|
randSeed int64 = 123456789
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
errMockLookupHost = errors.New("this is a mocked error")
|
||
|
testLookupResult = []string{ip1, ip2, ip3}
|
||
|
testLookupResultWithPort = []string{net.JoinHostPort(ip1, testPort), net.JoinHostPort(ip2, testPort), net.JoinHostPort(ip3, testPort)}
|
||
|
)
|
||
|
|
||
|
type mockDialContext struct {
|
||
|
mock.Mock
|
||
|
|
||
|
addrFrequencyMu sync.Mutex
|
||
|
addrFrequency map[string]int
|
||
|
}
|
||
|
|
||
|
func newMockDialContext(acceptableAddresses []string) *mockDialContext {
|
||
|
m := &mockDialContext{
|
||
|
addrFrequencyMu: sync.Mutex{},
|
||
|
addrFrequency: make(map[string]int),
|
||
|
}
|
||
|
for _, acceptableAddr := range acceptableAddresses {
|
||
|
m.On("dialContext", mock.Anything, mock.Anything, acceptableAddr).Return(nil, nil)
|
||
|
}
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
func (dc *mockDialContext) dialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||
|
dc.addrFrequencyMu.Lock()
|
||
|
defer dc.addrFrequencyMu.Unlock()
|
||
|
args := dc.MethodCalled("dialContext", ctx, network, addr)
|
||
|
dc.addrFrequency[addr]++
|
||
|
return nil, args.Error(1)
|
||
|
}
|
||
|
|
||
|
func (dc *mockDialContext) getCount(addr string) int {
|
||
|
dc.addrFrequencyMu.Lock()
|
||
|
defer dc.addrFrequencyMu.Unlock()
|
||
|
return dc.addrFrequency[addr]
|
||
|
}
|
||
|
|
||
|
type mockedLookupHost struct {
|
||
|
withErr bool
|
||
|
result []string
|
||
|
}
|
||
|
|
||
|
func (lh *mockedLookupHost) LookupHost(context.Context, string) ([]string, error) {
|
||
|
if lh.withErr {
|
||
|
return nil, errMockLookupHost
|
||
|
}
|
||
|
return lh.result, nil
|
||
|
}
|
||
|
|
||
|
func createDialContextWithRoundRobinDNS(dialContext config.DialContextFunc, resolver hostResolver, r *rand.Rand) dialContextWithRoundRobinDNS {
|
||
|
return dialContextWithRoundRobinDNS{
|
||
|
dialContext: dialContext,
|
||
|
resolver: resolver,
|
||
|
rand: r,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDialContextWithRandomConnections(t *testing.T) {
|
||
|
numberOfRuns := 2 * len(testLookupResult)
|
||
|
var mdc *mockDialContext
|
||
|
testCases := map[string]struct {
|
||
|
addr string
|
||
|
setup func() dialContextWithRoundRobinDNS
|
||
|
check func()
|
||
|
}{
|
||
|
"if address contains no port call default DealContext": {
|
||
|
addr: testAddrWithoutPort,
|
||
|
setup: func() dialContextWithRoundRobinDNS {
|
||
|
mdc = newMockDialContext([]string{testAddrWithoutPort})
|
||
|
return createDialContextWithRoundRobinDNS(mdc.dialContext, &mockedLookupHost{withErr: false}, rand.New(rand.NewSource(time.Now().Unix())))
|
||
|
},
|
||
|
check: func() {
|
||
|
require.Equal(t, numberOfRuns, mdc.getCount(testAddrWithoutPort))
|
||
|
},
|
||
|
},
|
||
|
"if lookup host returns error call default DealContext": {
|
||
|
addr: testAddrWithPort,
|
||
|
setup: func() dialContextWithRoundRobinDNS {
|
||
|
mdc = newMockDialContext([]string{testAddrWithPort})
|
||
|
return createDialContextWithRoundRobinDNS(mdc.dialContext, &mockedLookupHost{withErr: true}, rand.New(rand.NewSource(time.Now().Unix())))
|
||
|
},
|
||
|
check: func() {
|
||
|
require.Equal(t, numberOfRuns, mdc.getCount(testAddrWithPort))
|
||
|
},
|
||
|
},
|
||
|
"if lookup returns no addresses call default DealContext": {
|
||
|
addr: testAddrWithPort,
|
||
|
setup: func() dialContextWithRoundRobinDNS {
|
||
|
mdc = newMockDialContext([]string{testAddrWithPort})
|
||
|
return createDialContextWithRoundRobinDNS(mdc.dialContext, &mockedLookupHost{}, rand.New(rand.NewSource(time.Now().Unix())))
|
||
|
},
|
||
|
check: func() {
|
||
|
require.Equal(t, numberOfRuns, mdc.getCount(testAddrWithPort))
|
||
|
},
|
||
|
},
|
||
|
"if lookup host is successful, shuffle results": {
|
||
|
addr: testAddrWithPort,
|
||
|
setup: func() dialContextWithRoundRobinDNS {
|
||
|
mdc = newMockDialContext(testLookupResultWithPort)
|
||
|
return createDialContextWithRoundRobinDNS(mdc.dialContext, &mockedLookupHost{result: testLookupResult}, rand.New(rand.NewSource(randSeed)))
|
||
|
},
|
||
|
check: func() {
|
||
|
// we ensure that not all runs will choose the first element of the lookup
|
||
|
require.NotEqual(t, numberOfRuns, mdc.getCount(testLookupResultWithPort[0]))
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for name, tc := range testCases {
|
||
|
t.Run(name, func(t *testing.T) {
|
||
|
dc := tc.setup()
|
||
|
require.NotNil(t, dc)
|
||
|
for i := 0; i < numberOfRuns; i++ {
|
||
|
_, err := dc.dialContextFn()(context.Background(), testNetwork, tc.addr)
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
tc.check()
|
||
|
})
|
||
|
}
|
||
|
}
|