github.com/google/cloudprober@v0.11.3/probes/dns/dns_test.go (about)

     1  // Copyright 2017-2019 The Cloudprober Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dns
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/golang/protobuf/proto"
    24  	"github.com/google/cloudprober/logger"
    25  	"github.com/google/cloudprober/probes/common/statskeeper"
    26  	configpb "github.com/google/cloudprober/probes/dns/proto"
    27  	"github.com/google/cloudprober/probes/options"
    28  	"github.com/google/cloudprober/targets"
    29  	"github.com/google/cloudprober/validators"
    30  	validatorpb "github.com/google/cloudprober/validators/proto"
    31  	"github.com/miekg/dns"
    32  )
    33  
    34  // If question contains a bad domain or type, DNS query response status should
    35  // contain an error.
    36  const (
    37  	questionBadDomain    = "nosuchname"
    38  	questionBadType      = configpb.QueryType_CAA
    39  	answerContent        = " 3600 IN A 192.168.0.1"
    40  	answerMatchPattern   = "3600"
    41  	answerNoMatchPattern = "NAA"
    42  )
    43  
    44  var (
    45  	globalLog = logger.Logger{}
    46  )
    47  
    48  type mockClient struct{}
    49  
    50  // Exchange implementation that returns an error status if the query is for
    51  // questionBad[Domain|Type]. This allows us to check if query parameters are
    52  // populated correctly.
    53  func (*mockClient) Exchange(in *dns.Msg, fullTarget string) (*dns.Msg, time.Duration, error) {
    54  	if fullTarget != "8.8.8.8:53" {
    55  		return nil, 0, fmt.Errorf("unexpected target: %v", fullTarget)
    56  	}
    57  	out := &dns.Msg{}
    58  	question := in.Question[0]
    59  	if question.Name == questionBadDomain+"." || int(question.Qtype) == int(questionBadType) {
    60  		out.Rcode = dns.RcodeNameError
    61  	}
    62  	answerStr := question.Name + answerContent
    63  	a, err := dns.NewRR(answerStr)
    64  	if err != nil {
    65  		globalLog.Errorf("Error parsing answer \"%s\": %v", answerStr, err)
    66  	} else {
    67  		out.Answer = []dns.RR{a}
    68  	}
    69  	return out, time.Millisecond, nil
    70  }
    71  func (*mockClient) setReadTimeout(time.Duration) {}
    72  func (*mockClient) setSourceIP(net.IP)           {}
    73  
    74  func runProbe(t *testing.T, testName string, p *Probe, resolveF resolveFunc, total, success int64) {
    75  	p.client = new(mockClient)
    76  	p.targets = p.opts.Targets.ListEndpoints()
    77  
    78  	resultsChan := make(chan statskeeper.ProbeResult, len(p.targets))
    79  	p.runProbe(resultsChan, resolveF)
    80  
    81  	// The resultsChan output iterates through p.targets in the same order.
    82  	for _, target := range p.targets {
    83  		r := <-resultsChan
    84  		result := r.(probeRunResult)
    85  		if result.total.Int64() != total || result.success.Int64() != success {
    86  			t.Errorf("test(%s): result mismatch got (total, success) = (%d, %d), want (%d, %d)",
    87  				testName, result.total.Int64(), result.success.Int64(), total, success)
    88  		}
    89  		if result.Target() != target.Name {
    90  			t.Errorf("test(%s): unexpected target in probe result. got: %s, want: %s",
    91  				testName, result.Target(), target.Name)
    92  		}
    93  	}
    94  }
    95  
    96  func TestRun(t *testing.T) {
    97  	p := &Probe{}
    98  	opts := &options.Options{
    99  		Targets:   targets.StaticTargets("8.8.8.8"),
   100  		Interval:  2 * time.Second,
   101  		Timeout:   time.Second,
   102  		ProbeConf: &configpb.ProbeConf{},
   103  	}
   104  	if err := p.Init("dns_test", opts); err != nil {
   105  		t.Fatalf("Error creating probe: %v", err)
   106  	}
   107  	runProbe(t, "basic", p, nil, 1, 1)
   108  }
   109  
   110  func TestResolveFirst(t *testing.T) {
   111  	p := &Probe{}
   112  	opts := options.DefaultOptions()
   113  	opts.Targets = targets.StaticTargets("foo")
   114  	opts.ProbeConf = &configpb.ProbeConf{ResolveFirst: proto.Bool(true)}
   115  	if err := p.Init("dns_test_resolve_first", opts); err != nil {
   116  		t.Fatalf("Error creating probe: %v", err)
   117  	}
   118  
   119  	t.Run("success", func(t *testing.T) {
   120  		resolveF := func(target string, ipVer int) (net.IP, error) {
   121  			if target == "foo" {
   122  				return net.ParseIP("8.8.8.8"), nil
   123  			}
   124  			return nil, fmt.Errorf("resolve error")
   125  		}
   126  		runProbe(t, "resolve_first_success", p, resolveF, 1, 1)
   127  	})
   128  
   129  	t.Run("error", func(t *testing.T) {
   130  		resolveF := func(target string, ipVer int) (net.IP, error) {
   131  			return nil, fmt.Errorf("resolve error")
   132  		}
   133  		runProbe(t, "resolve_first_error", p, resolveF, 1, 0)
   134  	})
   135  }
   136  
   137  func TestProbeType(t *testing.T) {
   138  	p := &Probe{}
   139  	badType := questionBadType
   140  	opts := &options.Options{
   141  		Targets:  targets.StaticTargets("8.8.8.8"),
   142  		Interval: 2 * time.Second,
   143  		Timeout:  time.Second,
   144  		ProbeConf: &configpb.ProbeConf{
   145  			QueryType: &badType,
   146  		},
   147  	}
   148  	if err := p.Init("dns_probe_type_test", opts); err != nil {
   149  		t.Fatalf("Error creating probe: %v", err)
   150  	}
   151  	runProbe(t, "probetype", p, nil, 1, 0)
   152  }
   153  
   154  func TestBadName(t *testing.T) {
   155  	p := &Probe{}
   156  	opts := &options.Options{
   157  		Targets:  targets.StaticTargets("8.8.8.8"),
   158  		Interval: 2 * time.Second,
   159  		Timeout:  time.Second,
   160  		ProbeConf: &configpb.ProbeConf{
   161  			ResolvedDomain: proto.String(questionBadDomain),
   162  		},
   163  	}
   164  	if err := p.Init("dns_bad_domain_test", opts); err != nil {
   165  		t.Fatalf("Error creating probe: %v", err)
   166  	}
   167  	runProbe(t, "baddomain", p, nil, 1, 0)
   168  }
   169  
   170  func TestAnswerCheck(t *testing.T) {
   171  	p := &Probe{}
   172  	opts := &options.Options{
   173  		Targets:  targets.StaticTargets("8.8.8.8"),
   174  		Interval: 2 * time.Second,
   175  		Timeout:  time.Second,
   176  		ProbeConf: &configpb.ProbeConf{
   177  			MinAnswers: proto.Uint32(1),
   178  		},
   179  	}
   180  	if err := p.Init("dns_probe_answer_check_test", opts); err != nil {
   181  		t.Fatalf("Error creating probe: %v", err)
   182  	}
   183  	// expect success minAnswers == num answers returned == 1.
   184  	runProbe(t, "matchminanswers", p, nil, 1, 1)
   185  
   186  	opts.ProbeConf = &configpb.ProbeConf{
   187  		MinAnswers: proto.Uint32(2),
   188  	}
   189  	if err := p.Init("dns_probe_answer_check_test", opts); err != nil {
   190  		t.Fatalf("Error creating probe: %v", err)
   191  	}
   192  	// expect failure because only one answer returned and two wanted.
   193  	runProbe(t, "toofewanswers", p, nil, 1, 0)
   194  }
   195  
   196  func TestValidator(t *testing.T) {
   197  	p := &Probe{}
   198  	for _, tst := range []struct {
   199  		name      string
   200  		pattern   string
   201  		successCt int64
   202  	}{
   203  		{"match", answerMatchPattern, 1},
   204  		{"nomatch", answerNoMatchPattern, 0},
   205  	} {
   206  		valPb := []*validatorpb.Validator{{Name: proto.String(tst.name), Type: &validatorpb.Validator_Regex{tst.pattern}}}
   207  		validator, err := validators.Init(valPb, nil)
   208  		if err != nil {
   209  			t.Fatalf("Error initializing validator for pattern %v: %v", tst.pattern, err)
   210  		}
   211  		opts := &options.Options{
   212  			Targets:    targets.StaticTargets("8.8.8.8"),
   213  			Interval:   2 * time.Second,
   214  			Timeout:    time.Second,
   215  			ProbeConf:  &configpb.ProbeConf{},
   216  			Validators: validator,
   217  		}
   218  		if err := p.Init("dns_probe_answer_"+tst.name, opts); err != nil {
   219  			t.Fatalf("Error creating probe: %v", err)
   220  		}
   221  		runProbe(t, tst.name, p, nil, 1, tst.successCt)
   222  	}
   223  }