diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2456a273..44444fec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,10 @@ The following commands delete generated files and compiled binaries: - `make clean-tools` removes all build tools - `make clean-all` removes all generated files and build tools -### Updating collector components +### Updating Collector components The file [`manifest.yaml`](./manifest.yaml) describes all components in the collector distribution. See https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/builder#configuration for details on the format of this file. + +When adding a component, an example configuration for the component must be added to `testbed/testdata/config-allcomponents.yaml` +to test that it works with a basic configuration. diff --git a/testbed/smoke/smoke_test.go b/testbed/smoke/smoke_test.go index a1e6f1f8..1879a4cb 100644 --- a/testbed/smoke/smoke_test.go +++ b/testbed/smoke/smoke_test.go @@ -19,32 +19,104 @@ import ( var execPath = "../../bin/dynatrace-otel-collector" func TestCollectorStarts(t *testing.T) { - col := testbed.NewChildProcessCollector(testbed.WithAgentExePath(execPath)) - - cfg, err := os.ReadFile("../testdata/config-smoke.yaml") - require.NoError(t, err) - - col.PrepareConfig(string(cfg)) + tests := []struct { + name string + configFile string + preCheck func(t *testing.T) + }{ + { + name: "Basic test", + configFile: "config-smoke.yaml", + }, + { + name: "All components", + configFile: "config-allcomponents.yaml", + preCheck: func(t *testing.T) { + components := getComponents(t) + + b, err := os.ReadFile("../testdata/config-allcomponents.yaml") + require.NoError(t, err) + testdataComponents := collectorConf{} + err = yaml.Unmarshal(b, &testdataComponents) + require.NoError(t, err) + + for _, c := range components.Receivers { + _, ok := testdataComponents.Receivers[string(c.Name)] + require.True(t, ok, "config-allcomponents.yaml is missing receiver "+c.Name) + } + + for _, c := range components.Processors { + _, ok := testdataComponents.Processors[string(c.Name)] + require.True(t, ok, "config-allcomponents.yaml is missing processor "+c.Name) + } + + for _, c := range components.Exporters { + _, ok := testdataComponents.Exporters[string(c.Name)] + require.True(t, ok, "config-allcomponents.yaml is missing exporter "+c.Name) + } + + for _, c := range components.Connectors { + _, ok := testdataComponents.Connectors[string(c.Name)] + require.True(t, ok, "config-allcomponents.yaml is missing connector "+c.Name) + } + + for _, c := range components.Extensions { + _, ok := testdataComponents.Extensions[string(c.Name)] + require.True(t, ok, "config-allcomponents.yaml is missing extension "+c.Name) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.preCheck != nil { + tt.preCheck(t) + } + + col := testbed.NewChildProcessCollector(testbed.WithAgentExePath(execPath)) + + cfg, err := os.ReadFile("../testdata/" + tt.configFile) + require.NoError(t, err) + + col.PrepareConfig(string(cfg)) + + err = col.Start(testbed.StartParams{ + Name: "dynatrace-otel-collector", + LogFilePath: "col.log", + }) + require.NoError(t, err) + + var resp *http.Response + require.Eventually(t, func() bool { + resp, err = http.Get("http://localhost:9090/metrics") + + return err == nil + }, 3*time.Second, 1*time.Second) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Contains(t, string(body), "otelcol_process_uptime") + + stopped, _ := col.Stop() + require.True(t, stopped) + }) + } +} - err = col.Start(testbed.StartParams{ - Name: "dynatrace-otel-collector", - LogFilePath: "col.log", - }) +func TestCollectorIsBuiltFromManifest(t *testing.T) { + components := getComponents(t) + b, err := os.ReadFile("../../manifest.yaml") require.NoError(t, err) - - var resp *http.Response - require.Eventually(t, func() bool { - resp, err = http.Get("http://localhost:9090/metrics") - - return err == nil - }, 3*time.Second, 1*time.Second) - - body, err := io.ReadAll(resp.Body) + manifestComponents := manifest{} + err = yaml.Unmarshal(b, &manifestComponents) require.NoError(t, err) - require.Contains(t, string(body), "otelcol_process_uptime") - stopped, _ := col.Stop() - require.True(t, stopped) + assert.Equal(t, len(components.Connectors), len(manifestComponents.Connectors)) + assert.Equal(t, len(components.Exporters), len(manifestComponents.Exporters)) + assert.Equal(t, len(components.Extensions), len(manifestComponents.Extensions)) + assert.Equal(t, len(components.Processors), len(manifestComponents.Processors)) + assert.Equal(t, len(components.Receivers), len(manifestComponents.Receivers)) } type componentMetadata struct { @@ -73,7 +145,15 @@ type manifest struct { Extensions []gomod } -func TestCollectorIsBuiltFromManifest(t *testing.T) { +type collectorConf struct { + Receivers map[string]any + Processors map[string]any + Exporters map[string]any + Connectors map[string]any + Extensions map[string]any +} + +func getComponents(t *testing.T) componentsOutput { cmd := exec.Command(execPath, "components") var stdout bytes.Buffer @@ -87,15 +167,5 @@ func TestCollectorIsBuiltFromManifest(t *testing.T) { err = yaml.Unmarshal(output, &components) require.NoError(t, err) - b, err := os.ReadFile("../../manifest.yaml") - require.NoError(t, err) - manifestComponents := manifest{} - err = yaml.Unmarshal(b, &manifestComponents) - require.NoError(t, err) - - assert.Equal(t, len(components.Connectors), len(manifestComponents.Connectors)) - assert.Equal(t, len(components.Exporters), len(manifestComponents.Exporters)) - assert.Equal(t, len(components.Extensions), len(manifestComponents.Extensions)) - assert.Equal(t, len(components.Processors), len(manifestComponents.Processors)) - assert.Equal(t, len(components.Receivers), len(manifestComponents.Receivers)) + return components } diff --git a/testbed/testdata/config-allcomponents.yaml b/testbed/testdata/config-allcomponents.yaml new file mode 100644 index 00000000..ca9e0265 --- /dev/null +++ b/testbed/testdata/config-allcomponents.yaml @@ -0,0 +1,165 @@ +receivers: + filelog: + include: [/dev/null] + fluentforward: + endpoint: 0.0.0.0:8006 + hostmetrics: + scrapers: + cpu: + jaeger: + protocols: + grpc: + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 5s + static_configs: + - targets: ['0.0.0.0:8888'] + - job_name: k8s + kubernetes_sd_configs: + - role: pod + relabel_configs: + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + regex: "true" + action: keep + metric_relabel_configs: + - source_labels: [__name__] + regex: "(request_duration_seconds.*|response_duration_seconds.*)" + action: keep + syslog: + tcp: + listen_address: "0.0.0.0:54526" + protocol: rfc5424 + httpcheck: + targets: + - endpoint: http://endpoint:80 + method: GET + - endpoint: http://localhost:8080/health + method: GET + - endpoint: http://localhost:8081/health + method: POST + headers: + test-header: "test-value" + collection_interval: 10s + otlp: + protocols: + grpc: + endpoint: localhost:4317 + http: + endpoint: localhost:4318 + +processors: + attributes: + actions: + - key: db.table + action: delete + - key: redacted_span + value: true + action: upsert + - key: copy_key + from_attribute: key_original + action: update + - key: account_id + value: 2245 + action: insert + - key: account_password + action: delete + - key: account_email + action: hash + - key: http.status_code + action: convert + converted_type: int + batch: + cumulativetodelta: + filter: + error_mode: ignore + traces: + span: + - 'attributes["container.name"] == "app_container_1"' + - 'resource.attributes["host.name"] == "localhost"' + - 'name == "app_3"' + spanevent: + - 'attributes["grpc"] == true' + - 'IsMatch(name, ".*grpc.*")' + metrics: + metric: + - 'name == "my.metric" and resource.attributes["my_label"] == "abc123"' + - 'type == METRIC_DATA_TYPE_HISTOGRAM' + datapoint: + - 'metric.type == METRIC_DATA_TYPE_SUMMARY' + - 'resource.attributes["service.name"] == "my_service_name"' + logs: + log_record: + - 'IsMatch(body, ".*password.*")' + - 'severity_number < SEVERITY_NUMBER_WARN' + k8sattributes: + memory_limiter: + check_interval: 1s + limit_mib: 4000 + spike_limit_mib: 800 + probabilistic_sampler: + resourcedetection: + detectors: [env] + timeout: 2s + override: false + resource: + attributes: + - key: cloud.availability_zone + value: "zone-1" + action: upsert + - key: k8s.cluster.name + from_attribute: k8s-cluster + action: insert + - key: redundant-attribute + action: delete + tail_sampling: + policies: + - name: keep-errors + type: status_code + status_code: {status_codes: [ERROR, UNSET]} + - name: keep-slow-traces + type: latency + latency: {threshold_ms: 500} + transform: + error_mode: ignore + trace_statements: + - context: span + statements: + - set(attributes["dt.test"], "otel-collector") + +exporters: + debug: + verbosity: detailed + otlp: + endpoint: https://localhost:4312 + otlphttp: + endpoint: https://localhost:7821 + +connectors: + forward: + spanmetrics: + +extensions: + zpages: + health_check: + endpoint: "localhost:13000" + tls: + ca_file: "/path/to/ca.crt" + cert_file: "/path/to/cert.crt" + key_file: "/path/to/key.key" + path: "/health/status" + check_collector_pipeline: + enabled: true + interval: "5m" + exporter_failure_threshold: 5 + +service: + pipelines: + logs: + receivers: [filelog] + exporters: [debug] + telemetry: + metrics: + level: normal + address: localhost:9090