Skip to content

Commit 0568a50

Browse files
authored
fix: Add a safety net in adhoc's defaultRunnerFactory (#1268)
This is trying to test for possible mistakes in setting up ad-hoc checks in the future. Signed-off-by: Marcelo E. Magallon <[email protected]>
1 parent 87d4781 commit 0568a50

File tree

2 files changed

+286
-5
lines changed

2 files changed

+286
-5
lines changed

Diff for: internal/adhoc/adhoc.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@ type Error string
5151
func (e Error) Error() string { return string(e) }
5252

5353
const (
54-
errNotAuthorized = Error("probe not authorized")
55-
errTransportClosing = Error("transport closing")
56-
errProbeUnregistered = Error("probe no longer registered")
57-
errIncompatibleApi = Error("API does not support required features")
54+
errNotAuthorized = Error("probe not authorized")
55+
errTransportClosing = Error("transport closing")
56+
errProbeUnregistered = Error("probe no longer registered")
57+
errIncompatibleApi = Error("API does not support required features")
58+
errInvalidAdHocRequest = Error("invalid ad-hoc request")
59+
60+
k6AdhocGraceTime = 20 * time.Second
5861
)
5962

6063
type runner struct {
@@ -358,6 +361,10 @@ func (h *Handler) handleAdHocCheck(ctx context.Context, ahReq *sm.AdHocRequest)
358361

359362
runner, err := h.runnerFactory(ctx, ahReq)
360363
if err != nil {
364+
// TODO(mem): if the runner factory returns an error, we should
365+
// create a result that reflects that and publish it, so that
366+
// the frontend is able to communicate to the user that
367+
// something went wrong.
361368
return err
362369
}
363370

@@ -382,12 +389,31 @@ func defaultGrpcAdhocChecksClientFactory(conn ClientConn) (sm.AdHocChecksClient,
382389
}
383390

384391
func (h *Handler) defaultRunnerFactory(ctx context.Context, req *sm.AdHocRequest) (*runner, error) {
392+
// This should never happen. If we hit this, it's a bug in the code
393+
// that is handling the request, or the API sent us an invalid request.
394+
if req == nil || req.AdHocCheck.TenantId == 0 {
395+
return nil, errInvalidAdHocRequest
396+
}
397+
385398
check := model.Check{
386399
Check: sm.Check{
387400
TenantId: req.AdHocCheck.TenantId,
388401
Target: req.AdHocCheck.Target,
389402
Timeout: req.AdHocCheck.Timeout,
390403
Settings: req.AdHocCheck.Settings,
404+
405+
// All the following fields are not used for ad-hoc checks.
406+
Id: 0, // ad-hoc checks don't have an ID in this sense.
407+
Job: "",
408+
Frequency: 0,
409+
Offset: 0,
410+
Enabled: true,
411+
Labels: nil,
412+
Probes: nil,
413+
BasicMetricsOnly: false,
414+
AlertSensitivity: "",
415+
Created: 0,
416+
Modified: 0,
391417
},
392418
}
393419

@@ -402,7 +428,6 @@ func (h *Handler) defaultRunnerFactory(ctx context.Context, req *sm.AdHocRequest
402428
timeout := time.Duration(check.Timeout) * time.Millisecond
403429
switch check.Type() {
404430
case sm.CheckTypeMultiHttp, sm.CheckTypeScripted, sm.CheckTypeBrowser:
405-
const k6AdhocGraceTime = 20 * time.Second
406431
timeout += k6AdhocGraceTime
407432
}
408433

Diff for: internal/adhoc/adhoc_test.go

+256
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package adhoc
22

33
import (
44
"context"
5+
"errors"
56
"io"
67
"os"
78
"testing"
@@ -17,6 +18,9 @@ import (
1718
"google.golang.org/grpc/status"
1819

1920
"github.com/grafana/synthetic-monitoring-agent/internal/feature"
21+
"github.com/grafana/synthetic-monitoring-agent/internal/k6runner"
22+
"github.com/grafana/synthetic-monitoring-agent/internal/model"
23+
"github.com/grafana/synthetic-monitoring-agent/internal/prober"
2024
"github.com/grafana/synthetic-monitoring-agent/internal/prober/logger"
2125
"github.com/grafana/synthetic-monitoring-agent/internal/pusher"
2226
sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
@@ -347,3 +351,255 @@ func (p *testProber) Probe(ctx context.Context, target string, registry *prometh
347351
_ = logger.Log("msg", "test")
348352
return true, 1
349353
}
354+
355+
// Add mock secrets store
356+
type testSecretStore struct{}
357+
358+
func (s testSecretStore) GetSecretCredentials(ctx context.Context, tenantId model.GlobalID) (*sm.SecretStore, error) {
359+
if tenantId == 0 {
360+
return nil, errors.New("invalid tenant ID")
361+
}
362+
363+
return &sm.SecretStore{
364+
Url: "http://example.com",
365+
Token: "test-token",
366+
}, nil
367+
}
368+
369+
func TestDefaultRunnerFactory(t *testing.T) {
370+
t.Parallel()
371+
372+
features := feature.NewCollection()
373+
require.NoError(t, features.Set(feature.K6))
374+
375+
logger := zerolog.New(io.Discard)
376+
if testing.Verbose() {
377+
logger = zerolog.New(os.Stdout)
378+
}
379+
380+
// Initialize the mockRunner and secretStore
381+
mockRunner := &testK6Runner{}
382+
secretStore := &testSecretStore{}
383+
384+
testcases := map[string]struct {
385+
request *sm.AdHocRequest
386+
expectError bool
387+
errCheck func(error) bool
388+
shouldPanic bool
389+
}{
390+
"ping check": {
391+
request: &sm.AdHocRequest{
392+
AdHocCheck: sm.AdHocCheck{
393+
Id: "test-ping",
394+
TenantId: 1000,
395+
Target: "example.com",
396+
Timeout: 1000,
397+
Settings: sm.CheckSettings{
398+
Ping: &sm.PingSettings{},
399+
},
400+
},
401+
},
402+
},
403+
"http check": {
404+
request: &sm.AdHocRequest{
405+
AdHocCheck: sm.AdHocCheck{
406+
Id: "test-http",
407+
TenantId: 1000,
408+
Target: "http://example.com",
409+
Timeout: 1000,
410+
Settings: sm.CheckSettings{
411+
Http: &sm.HttpSettings{},
412+
},
413+
},
414+
},
415+
},
416+
"dns check": {
417+
request: &sm.AdHocRequest{
418+
AdHocCheck: sm.AdHocCheck{
419+
Id: "test-dns",
420+
TenantId: 1000,
421+
Target: "example.com",
422+
Timeout: 1000,
423+
Settings: sm.CheckSettings{
424+
Dns: &sm.DnsSettings{},
425+
},
426+
},
427+
},
428+
},
429+
"tcp check": {
430+
request: &sm.AdHocRequest{
431+
AdHocCheck: sm.AdHocCheck{
432+
Id: "test-tcp",
433+
TenantId: 1000,
434+
Target: "example.com:80",
435+
Timeout: 1000,
436+
Settings: sm.CheckSettings{
437+
Tcp: &sm.TcpSettings{},
438+
},
439+
},
440+
},
441+
},
442+
"k6 scripted check": {
443+
request: &sm.AdHocRequest{
444+
AdHocCheck: sm.AdHocCheck{
445+
Id: "test-scripted",
446+
TenantId: 1000,
447+
Target: "test-target",
448+
Timeout: 1000,
449+
Settings: sm.CheckSettings{
450+
Scripted: &sm.ScriptedSettings{},
451+
},
452+
},
453+
},
454+
},
455+
"k6 multihttp check": {
456+
request: &sm.AdHocRequest{
457+
AdHocCheck: sm.AdHocCheck{
458+
Id: "test-multihttp",
459+
TenantId: 1000,
460+
Target: "test-target",
461+
Timeout: 1000,
462+
Settings: sm.CheckSettings{
463+
Multihttp: &sm.MultiHttpSettings{
464+
Entries: []*sm.MultiHttpEntry{
465+
{
466+
Request: &sm.MultiHttpEntryRequest{
467+
Url: "http://example.com",
468+
},
469+
},
470+
},
471+
},
472+
},
473+
},
474+
},
475+
},
476+
"k6 browser check": {
477+
request: &sm.AdHocRequest{
478+
AdHocCheck: sm.AdHocCheck{
479+
Id: "test-browser",
480+
TenantId: 1000,
481+
Target: "test-target",
482+
Timeout: 1000,
483+
Settings: sm.CheckSettings{
484+
Browser: &sm.BrowserSettings{},
485+
},
486+
},
487+
},
488+
},
489+
"zero timeout": {
490+
request: &sm.AdHocRequest{
491+
AdHocCheck: sm.AdHocCheck{
492+
Id: "test-zero-timeout",
493+
TenantId: 1000,
494+
Target: "example.com",
495+
Timeout: 0,
496+
Settings: sm.CheckSettings{
497+
Ping: &sm.PingSettings{},
498+
},
499+
},
500+
},
501+
},
502+
"empty settings": {
503+
request: &sm.AdHocRequest{
504+
AdHocCheck: sm.AdHocCheck{
505+
Id: "test-empty-settings",
506+
TenantId: 1000,
507+
Target: "example.com",
508+
Timeout: 1000,
509+
Settings: sm.CheckSettings{},
510+
},
511+
},
512+
expectError: true,
513+
shouldPanic: true,
514+
},
515+
"nil request": {
516+
request: nil,
517+
expectError: true,
518+
errCheck: func(err error) bool {
519+
return errors.Is(err, errInvalidAdHocRequest)
520+
},
521+
},
522+
"zero tenant": {
523+
request: &sm.AdHocRequest{
524+
AdHocCheck: sm.AdHocCheck{
525+
Id: "test-zero-tenant",
526+
TenantId: 0,
527+
Target: "example.com",
528+
Timeout: 1000,
529+
Settings: sm.CheckSettings{},
530+
},
531+
},
532+
expectError: true,
533+
errCheck: func(err error) bool {
534+
return errors.Is(err, errInvalidAdHocRequest)
535+
},
536+
},
537+
}
538+
539+
for name, tc := range testcases {
540+
tc := tc
541+
t.Run(name, func(t *testing.T) {
542+
t.Parallel()
543+
544+
h := &Handler{
545+
logger: logger,
546+
features: features,
547+
probe: &sm.Probe{
548+
Name: "test-probe",
549+
},
550+
proberFactory: prober.NewProberFactory(mockRunner, 0, features, secretStore),
551+
}
552+
553+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
554+
defer cancel()
555+
556+
if tc.shouldPanic {
557+
require.Panics(t, func() {
558+
_, _ = h.defaultRunnerFactory(ctx, tc.request)
559+
})
560+
return
561+
}
562+
563+
runner, err := h.defaultRunnerFactory(ctx, tc.request)
564+
if tc.expectError {
565+
require.Error(t, err)
566+
if tc.errCheck != nil {
567+
require.True(t, tc.errCheck(err), "unexpected error: %v", err)
568+
}
569+
require.Nil(t, runner)
570+
return
571+
}
572+
573+
require.NoError(t, err)
574+
require.NotNil(t, runner)
575+
576+
// Verify runner fields
577+
require.Equal(t, logger, runner.logger)
578+
require.NotNil(t, runner.prober)
579+
require.Equal(t, tc.request.AdHocCheck.Id, runner.id)
580+
require.Equal(t, "test-probe", runner.probe)
581+
582+
// For k6-based checks, verify the grace period is added
583+
switch tc.request.AdHocCheck.Type() {
584+
case sm.CheckTypeMultiHttp, sm.CheckTypeScripted, sm.CheckTypeBrowser:
585+
expectedTimeout := time.Duration(tc.request.AdHocCheck.Timeout)*time.Millisecond + k6AdhocGraceTime
586+
require.Equal(t, expectedTimeout, runner.timeout)
587+
588+
default:
589+
expectedTimeout := time.Duration(tc.request.AdHocCheck.Timeout) * time.Millisecond
590+
require.Equal(t, expectedTimeout, runner.timeout)
591+
}
592+
})
593+
}
594+
}
595+
596+
// Add mock k6runner
597+
type testK6Runner struct{}
598+
599+
func (r *testK6Runner) WithLogger(logger *zerolog.Logger) k6runner.Runner {
600+
return r
601+
}
602+
603+
func (r *testK6Runner) Run(ctx context.Context, script k6runner.Script) (*k6runner.RunResponse, error) {
604+
return &k6runner.RunResponse{}, nil
605+
}

0 commit comments

Comments
 (0)