github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/prober/dns/dns_test.go (about)

     1  package dns
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"net"
     7  	"os"
     8  	"slices"
     9  	"strings"
    10  	"sync/atomic"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/go-kit/log"
    15  	"github.com/grafana/synthetic-monitoring-agent/internal/model"
    16  	"github.com/grafana/synthetic-monitoring-agent/internal/prober/dns/internal/bbe/config"
    17  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    18  	"github.com/miekg/dns"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"github.com/prometheus/common/expfmt"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestName(t *testing.T) {
    25  	name := Prober.Name(Prober{})
    26  	require.Equal(t, name, "dns")
    27  }
    28  
    29  func TestNewProber(t *testing.T) {
    30  	testcases := map[string]struct {
    31  		input       model.Check
    32  		expected    Prober
    33  		ExpectError bool
    34  	}{
    35  		"default": {
    36  			input: model.Check{Check: sm.Check{
    37  				Target: "www.grafana.com",
    38  				Settings: sm.CheckSettings{
    39  					Dns: &sm.DnsSettings{},
    40  				},
    41  			}},
    42  			expected: Prober{
    43  				config: config.Module{
    44  					Prober:  "dns",
    45  					Timeout: 0,
    46  					DNS: config.DNSProbe{
    47  						IPProtocol:         "ip6",
    48  						IPProtocolFallback: true,
    49  						TransportProtocol:  "tcp",
    50  						QueryName:          "www.grafana.com",
    51  						QueryType:          "ANY",
    52  						Recursion:          true,
    53  					},
    54  				},
    55  			},
    56  			ExpectError: false,
    57  		},
    58  		"no-settings": {
    59  			input: model.Check{
    60  				Check: sm.Check{
    61  					Target: "www.grafana.com",
    62  					Settings: sm.CheckSettings{
    63  						Dns: nil,
    64  					},
    65  				},
    66  			},
    67  			expected:    Prober{},
    68  			ExpectError: true,
    69  		},
    70  	}
    71  
    72  	for name, testcase := range testcases {
    73  		t.Run(name, func(t *testing.T) {
    74  			actual, err := NewProber(testcase.input)
    75  			require.Equal(t, &testcase.expected, &actual)
    76  
    77  			if testcase.ExpectError {
    78  				require.Error(t, err, "unsupported check")
    79  			} else {
    80  				require.NoError(t, err)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestSettingsToModule(t *testing.T) {
    87  	testcases := map[string]struct {
    88  		input    sm.DnsSettings
    89  		expected config.Module
    90  	}{
    91  		"default": {
    92  			input: sm.DnsSettings{},
    93  			expected: config.Module{
    94  				Prober:  "dns",
    95  				Timeout: 0,
    96  				DNS: config.DNSProbe{
    97  					IPProtocol:         "ip6",
    98  					IPProtocolFallback: true,
    99  					TransportProtocol:  "tcp",
   100  					QueryName:          "www.grafana.com",
   101  					QueryType:          "ANY",
   102  					Recursion:          true,
   103  				},
   104  			},
   105  		},
   106  		"partial-settings": {
   107  			input: sm.DnsSettings{
   108  				RecordType: 4,
   109  				Protocol:   1,
   110  			},
   111  			expected: config.Module{
   112  				Prober:  "dns",
   113  				Timeout: 0,
   114  				DNS: config.DNSProbe{
   115  					IPProtocol:         "ip6",
   116  					IPProtocolFallback: true,
   117  					TransportProtocol:  "udp",
   118  					QueryName:          "www.grafana.com",
   119  					QueryType:          "MX",
   120  					Recursion:          true,
   121  				},
   122  			},
   123  		},
   124  		"validations": {
   125  			input: sm.DnsSettings{
   126  				RecordType: 4,
   127  				Protocol:   1,
   128  				ValidateAnswer: &sm.DNSRRValidator{
   129  					FailIfMatchesRegexp:    []string{"test"},
   130  					FailIfNotMatchesRegexp: []string{"not test"},
   131  				},
   132  				ValidateAuthority: &sm.DNSRRValidator{
   133  					FailIfMatchesRegexp:    []string{"test"},
   134  					FailIfNotMatchesRegexp: []string{"not test"},
   135  				},
   136  				ValidateAdditional: &sm.DNSRRValidator{
   137  					FailIfMatchesRegexp:    []string{"test"},
   138  					FailIfNotMatchesRegexp: []string{"not test"},
   139  				},
   140  			},
   141  			expected: config.Module{
   142  				Prober:  "dns",
   143  				Timeout: 0,
   144  				DNS: config.DNSProbe{
   145  					IPProtocol:         "ip6",
   146  					IPProtocolFallback: true,
   147  					TransportProtocol:  "udp",
   148  					QueryName:          "www.grafana.com",
   149  					QueryType:          "MX",
   150  					Recursion:          true,
   151  					ValidateAnswer: config.DNSRRValidator{
   152  						FailIfMatchesRegexp:    []string{"test"},
   153  						FailIfNotMatchesRegexp: []string{"not test"},
   154  					},
   155  					ValidateAuthority: config.DNSRRValidator{
   156  						FailIfMatchesRegexp:    []string{"test"},
   157  						FailIfNotMatchesRegexp: []string{"not test"},
   158  					},
   159  					ValidateAdditional: config.DNSRRValidator{
   160  						FailIfMatchesRegexp:    []string{"test"},
   161  						FailIfNotMatchesRegexp: []string{"not test"},
   162  					},
   163  				},
   164  			},
   165  		},
   166  	}
   167  
   168  	for name, testcase := range testcases {
   169  		target := "www.grafana.com"
   170  
   171  		t.Run(name, func(t *testing.T) {
   172  			actual := settingsToModule(&testcase.input, target)
   173  			require.Equal(t, &testcase.expected, &actual)
   174  		})
   175  	}
   176  }
   177  
   178  func TestProberRetries(t *testing.T) {
   179  	if !slices.Contains(strings.Split(os.Getenv("SM_TEST_RUN"), ","), "TestProberRetries") {
   180  		t.Skip("Skipping long test TestProberRetries")
   181  	}
   182  
   183  	mux := dns.NewServeMux()
   184  
   185  	var counter atomic.Int32
   186  
   187  	mux.Handle(".", dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
   188  		answer := dns.Msg{
   189  			MsgHdr: dns.MsgHdr{
   190  				Id:       r.Id,
   191  				Response: true,
   192  				Rcode:    dns.RcodeSuccess,
   193  			},
   194  			Question: r.Question,
   195  			Answer: []dns.RR{
   196  				&dns.A{
   197  					Hdr: dns.RR_Header{
   198  						Name:   r.Question[0].Name,
   199  						Rrtype: dns.TypeA,
   200  						Class:  dns.ClassINET,
   201  						Ttl:    60,
   202  					},
   203  					A: net.ParseIP("1.2.3.4"),
   204  				},
   205  			},
   206  		}
   207  
   208  		counter.Add(1)
   209  		t.Logf("Received request %d: %v", counter.Load(), r)
   210  
   211  		delay := 10 * time.Second
   212  		if counter.Load()%3 == 0 {
   213  			delay = 0
   214  		}
   215  
   216  		t.Log("Answer...")
   217  		time.Sleep(delay)
   218  
   219  		_ = w.WriteMsg(&answer)
   220  	}))
   221  
   222  	addr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:0")
   223  	require.NoError(t, err)
   224  	l, err := net.ListenUDP("udp4", addr)
   225  	require.NoError(t, err)
   226  	t.Log(l.LocalAddr().String())
   227  	server := &dns.Server{Addr: ":0", PacketConn: l, Net: "udp", Handler: mux}
   228  
   229  	go func() {
   230  		err := server.ActivateAndServe()
   231  		if err != nil {
   232  			panic(err)
   233  		}
   234  	}()
   235  
   236  	p, err := NewExperimentalProber(model.Check{
   237  		Check: sm.Check{
   238  			Target:  "www.grafana.com",
   239  			Timeout: 20000,
   240  			Settings: sm.CheckSettings{
   241  				Dns: &sm.DnsSettings{
   242  					Server:     l.LocalAddr().String(),
   243  					RecordType: sm.DnsRecordType_A,
   244  					Protocol:   sm.DnsProtocol_UDP,
   245  					IpVersion:  sm.IpVersion_V4,
   246  				},
   247  			},
   248  		},
   249  	})
   250  
   251  	require.NoError(t, err)
   252  
   253  	ctx, cancel := context.WithTimeout(context.Background(), p.config.Timeout)
   254  	t.Cleanup(cancel)
   255  
   256  	defer func() {
   257  		err := server.ShutdownContext(ctx)
   258  		if err != nil {
   259  			panic(err)
   260  		}
   261  	}()
   262  
   263  	registry := prometheus.NewPedanticRegistry()
   264  
   265  	var buf bytes.Buffer
   266  
   267  	logger := log.NewLogfmtLogger(&buf)
   268  
   269  	t0 := time.Now()
   270  	success, duration := p.Probe(ctx, p.target, registry, logger, "test-execution-id")
   271  	t.Log(success, time.Since(t0))
   272  	require.True(t, success)
   273  	require.Equal(t, 0, duration)
   274  
   275  	mfs, err := registry.Gather()
   276  	require.NoError(t, err)
   277  
   278  	enc := expfmt.NewEncoder(&buf, expfmt.NewFormat(expfmt.TypeTextPlain))
   279  	for _, mf := range mfs {
   280  		require.NoError(t, enc.Encode(mf))
   281  	}
   282  
   283  	t.Log(buf.String())
   284  }