diff --git a/internal/checks/checks.go b/internal/checks/checks.go index 1b3c4dc8..7413be23 100644 --- a/internal/checks/checks.go +++ b/internal/checks/checks.go @@ -22,6 +22,7 @@ import ( "google.golang.org/grpc/status" logproto "github.com/grafana/loki/pkg/push" + "github.com/grafana/synthetic-monitoring-agent/internal/error_types" "github.com/grafana/synthetic-monitoring-agent/internal/feature" "github.com/grafana/synthetic-monitoring-agent/internal/k6runner" "github.com/grafana/synthetic-monitoring-agent/internal/limits" @@ -34,23 +35,11 @@ import ( sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" ) -type Error string +type Error = error_types.BasicError -func (e Error) Error() string { return string(e) } +type TransientError = error_types.TransientError -// TransientError is an error that can be recovered. -type TransientError Error - -func (e TransientError) Error() string { return string(e) } - -var _ error = TransientError("") - -// FatalError is an error that causes the program to terminate. -type FatalError Error - -func (e FatalError) Error() string { return string(e) } - -var _ error = FatalError("") +type FatalError = error_types.FatalError const ( errCapabilityK6Missing = FatalError("k6 is required for scripted check support - configure k6 or edit probe capabilities in the SM app") diff --git a/internal/error_types/error_types.go b/internal/error_types/error_types.go new file mode 100644 index 00000000..56b66a53 --- /dev/null +++ b/internal/error_types/error_types.go @@ -0,0 +1,22 @@ +package error_types + +// BasicError is an error for which we have no additional information. +type BasicError string + +func (e BasicError) Error() string { return string(e) } + +var _ error = BasicError("") + +// TransientError is an error that can be recovered. +type TransientError BasicError + +func (e TransientError) Error() string { return string(e) } + +var _ error = TransientError("") + +// FatalError is an error that causes the program to terminate. +type FatalError BasicError + +func (e FatalError) Error() string { return string(e) } + +var _ error = FatalError("") diff --git a/internal/prober/prober.go b/internal/prober/prober.go index 93444c8f..9a63c609 100644 --- a/internal/prober/prober.go +++ b/internal/prober/prober.go @@ -5,8 +5,10 @@ import ( "fmt" "net/http" + "github.com/grafana/synthetic-monitoring-agent/internal/error_types" "github.com/grafana/synthetic-monitoring-agent/internal/k6runner" "github.com/grafana/synthetic-monitoring-agent/internal/model" + "github.com/grafana/synthetic-monitoring-agent/internal/prober/browser" "github.com/grafana/synthetic-monitoring-agent/internal/prober/dns" "github.com/grafana/synthetic-monitoring-agent/internal/prober/grpc" httpProber "github.com/grafana/synthetic-monitoring-agent/internal/prober/http" @@ -21,6 +23,8 @@ import ( "github.com/rs/zerolog" ) +const unsupportedCheckType = error_types.BasicError("unsupported check type") + type Prober interface { Name() string Probe(ctx context.Context, target string, registry *prometheus.Registry, logger logger.Logger) bool @@ -83,6 +87,17 @@ func (f proberFactory) New(ctx context.Context, logger zerolog.Logger, check mod err = fmt.Errorf("k6 checks are not enabled") } + case sm.CheckTypeBrowser: + // TODO(mem): we possibly need to extend the validation so that + // we know that the runner is actually able to handle browser + // checks. + if f.runner != nil { + p, err = browser.NewProber(ctx, check.Check, logger, f.runner) + target = check.Target + } else { + err = fmt.Errorf("k6 checks are not enabled") + } + case sm.CheckTypeMultiHttp: if f.runner != nil { reservedHeaders := f.getReservedHeaders(&check) @@ -97,7 +112,7 @@ func (f proberFactory) New(ctx context.Context, logger zerolog.Logger, check mod target = check.Target default: - return nil, "", fmt.Errorf("unsupported check type") + return nil, "", unsupportedCheckType } return p, target, err diff --git a/internal/prober/prober_test.go b/internal/prober/prober_test.go index 8219d210..dc8287b3 100644 --- a/internal/prober/prober_test.go +++ b/internal/prober/prober_test.go @@ -1 +1,28 @@ package prober + +import ( + "context" + "testing" + + "github.com/grafana/synthetic-monitoring-agent/internal/model" + sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +func TestProberFactoryCoverage(t *testing.T) { + // This test will assert that the prober factory is handling all the + // known check types (as defined in the synthetic_monitoring package). + + pf := NewProberFactory(nil, 0) + ctx := context.Background() + testLogger := zerolog.New(zerolog.NewTestWriter(t)) + + for _, checkType := range sm.CheckTypeValues() { + var check model.Check + require.NoError(t, check.FromSM(sm.GetCheckInstance(checkType))) + + _, _, err := pf.New(ctx, testLogger, check) + require.NotErrorIs(t, err, unsupportedCheckType) + } +} diff --git a/pkg/pb/synthetic_monitoring/checks_extra.go b/pkg/pb/synthetic_monitoring/checks_extra.go index dd31a1ae..66a55b40 100644 --- a/pkg/pb/synthetic_monitoring/checks_extra.go +++ b/pkg/pb/synthetic_monitoring/checks_extra.go @@ -1440,3 +1440,139 @@ func validateTimeout(checkType CheckType, timeout, frequency int64) error { func inClosedRange[T constraints.Ordered](v, lower, upper T) bool { return v >= lower && v <= upper } + +func GetCheckInstance(checkType CheckType) Check { + var validCheckCases = map[CheckType]Check{ + CheckTypeDns: { + Id: 1, + TenantId: 1, + Target: "www.example.org", + Job: "job", + Frequency: 1000, + Timeout: 1000, + Probes: []int64{1}, + Settings: CheckSettings{ + Dns: &DnsSettings{ + Server: "127.0.0.1", + }, + }, + }, + CheckTypeHttp: { + Id: 1, + TenantId: 1, + Target: "http://www.example.org", + Job: "job", + Frequency: 1000, + Timeout: 1000, + Probes: []int64{1}, + Settings: CheckSettings{ + Http: &HttpSettings{}, + }, + }, + CheckTypePing: { + Id: 1, + TenantId: 1, + Target: "127.0.0.1", + Job: "job", + Frequency: 1000, + Timeout: 1000, + Probes: []int64{1}, + Settings: CheckSettings{ + Ping: &PingSettings{}, + }, + }, + CheckTypeTcp: { + Id: 1, + TenantId: 1, + Target: "127.0.0.1:9000", + Job: "job", + Frequency: 1000, + Timeout: 1000, + Probes: []int64{1}, + Settings: CheckSettings{ + Tcp: &TcpSettings{}, + }, + }, + CheckTypeTraceroute: { + Id: 1, + TenantId: 1, + Target: "127.0.0.1", + Job: "job", + Frequency: 120000, + Timeout: 30000, + Probes: []int64{1}, + Settings: CheckSettings{ + Traceroute: &TracerouteSettings{}, + }, + }, + CheckTypeScripted: { + Id: 1, + TenantId: 1, + Target: "http://www.example.org", + Job: "job", + Frequency: 60000, + Timeout: 10000, + Probes: []int64{1}, + Settings: CheckSettings{ + Scripted: &ScriptedSettings{ + Script: []byte("// test"), + }, + }, + }, + CheckTypeMultiHttp: { + Id: 1, + TenantId: 1, + Target: "http://www.example.org", + Job: "job", + Frequency: 60000, + Timeout: 10000, + Probes: []int64{1}, + Settings: CheckSettings{ + Multihttp: &MultiHttpSettings{ + Entries: []*MultiHttpEntry{ + { + Request: &MultiHttpEntryRequest{ + Method: HttpMethod_GET, + Url: "http://www.example.org/", + }, + }, + }, + }, + }, + }, + CheckTypeGrpc: { + Id: 1, + TenantId: 1, + Target: "127.0.0.1:9000", + Job: "job", + Frequency: 60000, + Timeout: 10000, + Probes: []int64{1}, + Settings: CheckSettings{ + Grpc: &GrpcSettings{}, + }, + }, + CheckTypeBrowser: { + Id: 1, + TenantId: 1, + Target: "http://www.example.org", + Job: "job", + Frequency: 60000, + Timeout: 10000, + Probes: []int64{1}, + Settings: CheckSettings{ + Browser: &BrowserSettings{ + Script: []byte("// test"), + }, + }, + }, + } + + instance, known := validCheckCases[checkType] + + if !known { + panic("unknown check type") + } + + return instance +} diff --git a/pkg/pb/synthetic_monitoring/checks_extra_test.go b/pkg/pb/synthetic_monitoring/checks_extra_test.go index 3d32c497..b855040f 100644 --- a/pkg/pb/synthetic_monitoring/checks_extra_test.go +++ b/pkg/pb/synthetic_monitoring/checks_extra_test.go @@ -12,132 +12,13 @@ import ( var testDebugOutput = flag.Bool("test.debug-output", false, "include test debug output") -var validCheckCases = map[CheckType]Check{ - CheckTypeDns: { - Id: 1, - TenantId: 1, - Target: "www.example.org", - Job: "job", - Frequency: 1000, - Timeout: 1000, - Probes: []int64{1}, - Settings: CheckSettings{ - Dns: &DnsSettings{ - Server: "127.0.0.1", - }, - }, - }, - CheckTypeHttp: { - Id: 1, - TenantId: 1, - Target: "http://www.example.org", - Job: "job", - Frequency: 1000, - Timeout: 1000, - Probes: []int64{1}, - Settings: CheckSettings{ - Http: &HttpSettings{}, - }, - }, - CheckTypePing: { - Id: 1, - TenantId: 1, - Target: "127.0.0.1", - Job: "job", - Frequency: 1000, - Timeout: 1000, - Probes: []int64{1}, - Settings: CheckSettings{ - Ping: &PingSettings{}, - }, - }, - CheckTypeTcp: { - Id: 1, - TenantId: 1, - Target: "127.0.0.1:9000", - Job: "job", - Frequency: 1000, - Timeout: 1000, - Probes: []int64{1}, - Settings: CheckSettings{ - Tcp: &TcpSettings{}, - }, - }, - CheckTypeTraceroute: { - Id: 1, - TenantId: 1, - Target: "127.0.0.1", - Job: "job", - Frequency: 120000, - Timeout: 30000, - Probes: []int64{1}, - Settings: CheckSettings{ - Traceroute: &TracerouteSettings{}, - }, - }, - CheckTypeScripted: { - Id: 1, - TenantId: 1, - Target: "http://www.example.org", - Job: "job", - Frequency: 60000, - Timeout: 10000, - Probes: []int64{1}, - Settings: CheckSettings{ - Scripted: &ScriptedSettings{ - Script: []byte("// test"), - }, - }, - }, - CheckTypeMultiHttp: { - Id: 1, - TenantId: 1, - Target: "http://www.example.org", - Job: "job", - Frequency: 60000, - Timeout: 10000, - Probes: []int64{1}, - Settings: CheckSettings{ - Multihttp: &MultiHttpSettings{}, - }, - }, - CheckTypeGrpc: { - Id: 1, - TenantId: 1, - Target: "127.0.0.1:9000", - Job: "job", - Frequency: 60000, - Timeout: 10000, - Probes: []int64{1}, - Settings: CheckSettings{ - Grpc: &GrpcSettings{}, - }, - }, - CheckTypeBrowser: { - Id: 1, - TenantId: 1, - Target: "http://www.example.org", - Job: "job", - Frequency: 60000, - Timeout: 10000, - Probes: []int64{1}, - Settings: CheckSettings{ - Browser: &BrowserSettings{ - Script: []byte("// test"), - }, - }, - }, -} - func TestCheckValidate(t *testing.T) { - testcases := map[string]struct { + type TestCase struct { input Check expectError bool - }{ - "trivial ping": { - input: validCheckCases[CheckTypePing], - expectError: false, - }, + } + + testcases := map[string]TestCase{ "invalid tenant": { input: Check{ Id: 1, @@ -544,6 +425,14 @@ func TestCheckValidate(t *testing.T) { }, } + // add trivial cases for all check types + for _, checkType := range CheckTypeValues() { + testcases["valid "+checkType.String()] = TestCase{ + input: GetCheckInstance(checkType), + expectError: false, + } + } + for name, testcase := range testcases { t.Run(name, func(t *testing.T) { err := testcase.input.Validate() @@ -560,9 +449,9 @@ func TestCheckType(t *testing.T) { testcases := make(map[string]testcase) - for checkType, check := range validCheckCases { + for _, checkType := range CheckTypeValues() { testcases[checkType.String()] = testcase{ - input: check, + input: GetCheckInstance(checkType), expected: checkType, } } @@ -581,39 +470,39 @@ func TestCheckClass(t *testing.T) { expected CheckClass }{ CheckTypeDns.String(): { - input: validCheckCases[CheckTypeDns], + input: GetCheckInstance(CheckTypeDns), expected: CheckClass_PROTOCOL, }, CheckTypeHttp.String(): { - input: validCheckCases[CheckTypeHttp], + input: GetCheckInstance(CheckTypeHttp), expected: CheckClass_PROTOCOL, }, CheckTypePing.String(): { - input: validCheckCases[CheckTypePing], + input: GetCheckInstance(CheckTypePing), expected: CheckClass_PROTOCOL, }, CheckTypeTcp.String(): { - input: validCheckCases[CheckTypeTcp], + input: GetCheckInstance(CheckTypeTcp), expected: CheckClass_PROTOCOL, }, CheckTypeTraceroute.String(): { - input: validCheckCases[CheckTypeTraceroute], + input: GetCheckInstance(CheckTypeTraceroute), expected: CheckClass_PROTOCOL, }, CheckTypeScripted.String(): { - input: validCheckCases[CheckTypeScripted], + input: GetCheckInstance(CheckTypeScripted), expected: CheckClass_SCRIPTED, }, CheckTypeMultiHttp.String(): { - input: validCheckCases[CheckTypeMultiHttp], + input: GetCheckInstance(CheckTypeMultiHttp), expected: CheckClass_SCRIPTED, }, CheckTypeGrpc.String(): { - input: validCheckCases[CheckTypeGrpc], + input: GetCheckInstance(CheckTypeGrpc), expected: CheckClass_PROTOCOL, }, CheckTypeBrowser.String(): { - input: validCheckCases[CheckTypeBrowser], + input: GetCheckInstance(CheckTypeBrowser), expected: CheckClass_SCRIPTED, // Is this correct, or does this need to be CheckClass_Browser? }, } @@ -2075,3 +1964,12 @@ func TestInClosedRange(t *testing.T) { require.Equalf(t, tc.expected, actual, `%s`, name) } } + +func TestGetCheckInstance(t *testing.T) { + for _, checkType := range CheckTypeValues() { + check := GetCheckInstance(checkType) + require.NotNil(t, check) + require.Equal(t, checkType, check.Type()) + require.NoError(t, check.Validate()) + } +}