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 }