Skip to content

Commit

Permalink
Move resolver into separate file, fix tests by mocking the global Loo…
Browse files Browse the repository at this point in the history
…kupIP
  • Loading branch information
Ivan Mirić committed Sep 9, 2020
1 parent 7578a41 commit dbc8a9f
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 270 deletions.
78 changes: 45 additions & 33 deletions core/local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"net/url"
"reflect"
Expand Down Expand Up @@ -974,23 +975,9 @@ func TestExecutionSchedulerIsRunning(t *testing.T) {
assert.NoError(t, <-err)
}

type mockDNSRunner struct {
*js.Runner
resolver *testutils.MockResolver
}

// NewVU initializes a new VU replacing its resolver with a MockResolver.
func (r *mockDNSRunner) NewVU(id int64, out chan<- stats.SampleContainer) (lib.InitializedVU, error) {
initVU, err := r.Runner.NewVU(id, out)
if err != nil {
return nil, err
}
(initVU.(*js.VU)).Dialer.Resolver = r.resolver
return initVU, nil
}

func TestDNS(t *testing.T) {
t.Parallel()
// TestDNSResolver checks the DNS resolution behavior at the ExecutionScheduler level.
// It isn't run in parallel since it modifies the global netext.LookupIP.
func TestDNSResolver(t *testing.T) {
tb := httpmultibin.NewHTTPMultiBin(t)
defer tb.Cleanup()
sr := tb.Replacer.Replace
Expand All @@ -1016,7 +1003,7 @@ func TestDNS(t *testing.T) {
"inf": { // IPs are cached indefinitely
lib.Options{},
},
"no": { // every request does a DNS lookup
"0": { // every request does a DNS lookup
lib.Options{DNS: &lib.DNSConfig{
TTL: null.StringFrom("0"),
Strategy: lib.DefaultDNSConfig().Strategy,
Expand All @@ -1030,36 +1017,61 @@ func TestDNS(t *testing.T) {
},
}

checkLog := func(t *testing.T, entries []logrus.Entry) {
for _, entry := range entries {
expMsg := sr(`dial tcp 127.0.0.254:HTTPBIN_PORT: connect: connection refused`)
require.IsType(t, &url.Error{}, entry.Data["error"])
assert.EqualError(t, entry.Data["error"].(*url.Error).Err, expMsg)
}
}

for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
logger := logrus.New()
logger.SetOutput(testutils.NewTestOutput(t))
logger.SetOutput(ioutil.Discard)
logHook := testutils.SimpleLogrusHook{HookedLevels: []logrus.Level{logrus.WarnLevel}}
logger.AddHook(&logHook)

runner, err := js.New(logger, &loader.SourceData{
URL: &url.URL{Path: "/script.js"}, Data: []byte(script),
}, nil, lib.RuntimeOptions{})
require.NoError(t, err)

mr := &mockDNSRunner{runner, testutils.NewMockResolver(nil)}
ctx, cancel, execScheduler, samples := newTestExecutionScheduler(t, mr, logger, tc.opts)
ctx, cancel, execScheduler, samples := newTestExecutionScheduler(t, runner, logger, tc.opts)
defer cancel()

mr.resolver.Set("myhost", sr("HTTPBIN_IP"))
time.AfterFunc(1*time.Second, func() {
mr.resolver.Set("myhost", "127.0.0.254")
mr := testutils.NewMockResolver(nil)
defaultLookup := netext.LookupIP
netext.LookupIP = mr.LookupIPAll
defer func() { netext.LookupIP = defaultLookup }()

mr.Set("myhost", sr("HTTPBIN_IP"))
time.AfterFunc(3*time.Second, func() {
mr.Set("myhost", "127.0.0.254")
})

errCh := make(chan error, 1)
go func() { errCh <- execScheduler.Run(ctx, ctx, samples) }()

for {
select {
case err := <-errCh:
require.NoError(t, err)
return
case <-time.After(6 * time.Second):
t.Fatal("timed out")
return
select {
case err := <-errCh:
require.NoError(t, err)
entries := logHook.Drain()
switch name {
case "inf":
require.Len(t, entries, 0)
case "0":
require.Len(t, entries, 5)
checkLog(t, entries)
case "2s":
require.Len(t, entries, 2)
checkLog(t, entries)
}
return
case <-time.After(6 * time.Second):
t.Fatal("timed out")
return
}
})
}
Expand Down Expand Up @@ -1194,7 +1206,7 @@ func TestRealTimeAndSetupTeardownMetrics(t *testing.T) {
expTags = append(expTags, addExpTags...)
return netext.NewDialer(
net.Dialer{},
netext.NewDNSResolver(types.NullDurationFrom(0), lib.DNSFirst),
netext.NewResolver(types.NullDurationFrom(0), lib.DNSFirst),
).GetTrail(time.Now(), time.Now(),
true, emitIterations, getTags(expTags...))
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ require (
github.com/urfave/negroni v0.3.1-0.20180130044549-22c5532ea862
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,6 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8 h1:EVObHAr8DqpoJCVv6KYTle8FEImKhtkfcZetNqxDoJQ=
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
golang.org/x/crypto v0.0.0-20180308185624-c7dcf104e3a7 h1:c9Tyi4qyEZwEJ1+Zm6Fcqf+68wmUdMzfXYTp3s8Nzg8=
Expand Down
11 changes: 6 additions & 5 deletions js/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"math"
"net"
"net/http"
"net/http/cookiejar"
Expand Down Expand Up @@ -60,7 +61,7 @@ type Runner struct {
defaultGroup *lib.Group

BaseDialer net.Dialer
Resolver netext.DNSResolver
Resolver netext.Resolver
RPSLimit *rate.Limiter

console *console
Expand Down Expand Up @@ -105,7 +106,7 @@ func newFromBundle(logger *logrus.Logger, b *Bundle) (*Runner, error) {
DualStack: true,
},
console: newConsole(logger),
Resolver: netext.NewDNSResolver(
Resolver: netext.NewResolver(
types.NullDurationFrom(0),
lib.DefaultDNSConfig().Strategy.DNSStrategy,
),
Expand Down Expand Up @@ -336,7 +337,7 @@ func (r *Runner) SetOptions(opts lib.Options) error {
if err != nil {
return err
}
r.Resolver = netext.NewDNSResolver(ttl, opts.DNS.Strategy.DNSStrategy)
r.Resolver = netext.NewResolver(ttl, opts.DNS.Strategy.DNSStrategy)
}

return nil
Expand All @@ -346,8 +347,8 @@ func parseTTL(ttlS string) (types.NullDuration, error) {
ttl := types.NewNullDuration(0, false)
switch ttlS {
case "", "inf":
// cache indefinitely
ttl.Valid = true
// cache "indefinitely"
ttl = types.NullDurationFrom(math.MaxInt64)
case "0":
// disable cache
default:
Expand Down
82 changes: 3 additions & 79 deletions lib/netext/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,96 +26,20 @@ import (
"math/rand"
"net"
"strconv"
"sync"
"sync/atomic"
"time"

"github.com/pkg/errors"
"github.com/viki-org/dnscache"

"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/lib/types"
"github.com/loadimpact/k6/stats"
)

// DNSResolver is an interface that fetches DNS information about a given host.
type DNSResolver interface {
Fetch(host string) (net.IP, error)
}

type resolver struct {
cache *dnscache.Resolver
strategy lib.DNSStrategy
m sync.Mutex
roundRobin map[string]uint8
}

// NewDNSResolver returns a new DNS resolver. If ttl is a valid duration,
// responses will be cached for the specified period, or indefinitely if it's 0.
// The IP returned from Fetch() will be selected based on the given strategy.
func NewDNSResolver(ttl types.NullDuration, strategy lib.DNSStrategy) DNSResolver {
var cache *dnscache.Resolver
if ttl.Valid {
cache = dnscache.New(time.Duration(ttl.Duration))
}
return &resolver{
cache: cache,
strategy: strategy,
roundRobin: make(map[string]uint8),
}
}

// Fetch does a DNS lookup or uses the cache if enabled, and returns a single IP
// selected with the preset strategy.
func (r *resolver) Fetch(host string) (net.IP, error) {
var (
ips []net.IP
err error
)
if r.cache != nil {
ips, err = r.cache.Fetch(host)
} else {
ips, err = net.LookupIP(host)
}
if err != nil {
return nil, err
}

if len(ips) == 0 {
return nil, errors.Errorf("DNS lookup for %s returned 0 IPs", host)
}

return r.selectOne(host, ips), nil
}

func (r *resolver) selectOne(host string, ips []net.IP) net.IP {
switch r.strategy {
case lib.DNSFirst:
return ips[0]
case lib.DNSRoundRobin:
r.m.Lock()
defer func() {
r.roundRobin[host]++
r.m.Unlock()
}()
// TODO: The index approach is not stable and we'll need to track
// individual IPs... Do we really want to implement an additional cache
// for this, since dnscache does not expose its internal one? If so, we
// should scrap dnscache entirely and re-implement it here.
return ips[int(r.roundRobin[host])%len(ips)]
case lib.DNSRandom:
return ips[rand.Intn(len(ips))]
}
return nil
}

// Dialer wraps net.Dialer and provides k6 specific functionality -
// tracing, blacklists and DNS cache and aliases.
type Dialer struct {
net.Dialer

Resolver DNSResolver
Resolver Resolver
Blacklist []*lib.IPNet
Hosts map[string]*lib.HostAddress

Expand All @@ -124,7 +48,7 @@ type Dialer struct {
}

// NewDialer constructs a new Dialer with the given DNS resolver.
func NewDialer(dialer net.Dialer, resolver DNSResolver) *Dialer {
func NewDialer(dialer net.Dialer, resolver Resolver) *Dialer {
return &Dialer{
Dialer: dialer,
Resolver: resolver,
Expand Down Expand Up @@ -237,7 +161,7 @@ func (d *Dialer) findRemote(addr string) (*lib.HostAddress, error) {
return lib.NewHostAddress(ip, port)
}

ip, err = d.Resolver.Fetch(host)
ip, err = d.Resolver.LookupIP(host)
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions lib/netext/dialer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ func TestDialerAddr(t *testing.T) {

func newResolver() *testutils.MockResolver {
return testutils.NewMockResolver(
map[string]net.IP{
"example-resolver.com": net.ParseIP("1.2.3.4"),
"example-deny-resolver.com": net.ParseIP("8.9.10.11"),
"example-ipv6-deny-resolver.com": net.ParseIP("::1"),
map[string][]net.IP{
"example-resolver.com": {net.ParseIP("1.2.3.4")},
"example-deny-resolver.com": {net.ParseIP("8.9.10.11")},
"example-ipv6-deny-resolver.com": {net.ParseIP("::1")},
},
)
}
Loading

0 comments on commit dbc8a9f

Please sign in to comment.