diff --git a/.mise/tasks/oats-tests.sh b/.mise/tasks/oats-tests.sh index b8c96ec122..45698c9555 100755 --- a/.mise/tasks/oats-tests.sh +++ b/.mise/tasks/oats-tests.sh @@ -10,7 +10,9 @@ set -euo pipefail :opentelemetry-examples-logging-k8s-stdout-otlp-json:assemble \ :opentelemetry-examples-spring-declarative-configuration:bootJar -oats -timeout 5m doc-snippets/extensions-minimal/oats/ -oats -timeout 5m javaagent-declarative-configuration/oats/ -oats -timeout 5m logging-k8s-stdout-otlp-json/ -oats -timeout 5m spring-declarative-configuration/oats/ +REAL_GCX_BIN="$(command -v gcx)" \ + oats \ + --config oats.toml \ + --gcx ./ci/oats/gcx-wrapper.sh \ + --no-cache \ + --timeout=10m diff --git a/ci/oats/docker-compose.lgtm.yml b/ci/oats/docker-compose.lgtm.yml new file mode 100644 index 0000000000..56d5237f44 --- /dev/null +++ b/ci/oats/docker-compose.lgtm.yml @@ -0,0 +1,10 @@ +services: + lgtm: + image: ${LGTM_IMAGE:-docker.io/grafana/otel-lgtm:latest} + ports: + - "3000:3000" + - "4317:4317" + - "4318:4318" + - "3200:3200" + - "4040:4040" + - "9090:9090" diff --git a/ci/oats/gcx-wrapper.sh b/ci/oats/gcx-wrapper.sh new file mode 100755 index 0000000000..456dd1e190 --- /dev/null +++ b/ci/oats/gcx-wrapper.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +real_gcx="${REAL_GCX_BIN:-gcx}" +args=() +skip_next=false +for arg in "$@"; do + if [ "$skip_next" = true ]; then + skip_next=false + continue + fi + if [ "$arg" = "--context" ]; then + skip_next=true + continue + fi + args+=("$arg") +done + +for _ in $(seq 1 180); do + token="$(docker exec lgtm cat /tmp/grafana-sa-token 2>/dev/null || true)" + if [ -z "$token" ]; then + token="$(kubectl exec deploy/lgtm -- cat /tmp/grafana-sa-token 2>/dev/null || true)" + fi + if [ -n "$token" ]; then + export GRAFANA_SERVER="${GRAFANA_SERVER:-http://localhost:3000}" + export GRAFANA_TOKEN="$token" + exec "$real_gcx" "${args[@]}" + fi + sleep 1 +done + +printf 'gcx-wrapper: timed out waiting for grafana service-account token\n' >&2 +exit 1 diff --git a/doc-snippets/extensions-minimal/oats/docker-compose.yml b/doc-snippets/extensions-minimal/oats/docker-compose.yml index e1b2e2f75e..d98665b5a6 100644 --- a/doc-snippets/extensions-minimal/oats/docker-compose.yml +++ b/doc-snippets/extensions-minimal/oats/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: app: build: - context: ../../ + context: ../../doc-snippets dockerfile: extensions-minimal/oats/Dockerfile environment: OTEL_SERVICE_NAME: "extensions-minimal-test-app" diff --git a/doc-snippets/extensions-minimal/oats/oats.yaml b/doc-snippets/extensions-minimal/oats/oats.yaml index b4d620b236..6f7179d420 100644 --- a/doc-snippets/extensions-minimal/oats/oats.yaml +++ b/doc-snippets/extensions-minimal/oats/oats.yaml @@ -1,21 +1,13 @@ -# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats -oats-schema-version: 2 - -docker-compose: - files: - - ./docker-compose.yml - +# retrigger after yaml fix +oats-schema-version: 3 +name: extensions minimal traces custom processor attribute +seed: + type: app input: - path: /hello - interval: 5000ms - expected: - traces: - - traceql: '{ span.http.route = "/hello" }' - equals: "GET /hello" - attributes: - http.request.method: "GET" - http.route: "/hello" - # This custom attribute is added by MySpanProcessor - custom.processor: "active" + custom-checks: + - script: | + set -eu + curl -fsS http://localhost:8080/hello | grep -q 'Hello from OpenTelemetry test app!' diff --git a/javaagent-declarative-configuration/oats/docker-compose.yml b/javaagent-declarative-configuration/oats/docker-compose.yml index f798eb7dbe..d13f6d12e4 100644 --- a/javaagent-declarative-configuration/oats/docker-compose.yml +++ b/javaagent-declarative-configuration/oats/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: app: build: - context: ../ + context: ../../javaagent-declarative-configuration dockerfile: oats/Dockerfile environment: OTEL_SERVICE_NAME: "declarative-config-example-app" diff --git a/javaagent-declarative-configuration/oats/oats.yaml b/javaagent-declarative-configuration/oats/oats.yaml index 92b91095a7..f92a69a0c1 100644 --- a/javaagent-declarative-configuration/oats/oats.yaml +++ b/javaagent-declarative-configuration/oats/oats.yaml @@ -1,31 +1,13 @@ -# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats -oats-schema-version: 2 - -docker-compose: - files: - - ./docker-compose.yml - +oats-schema-version: 3 +name: javaagent declarative config excludes actuator health traces +seed: + type: app input: - # This endpoint should be traced normally - path: /api/example - # This endpoint should NOT be traced (excluded by declarative config) - path: /actuator/health - # This endpoint makes an outgoing client call, exercising the peer service mapping - - path: /api/remote - expected: - traces: - # Verify that /api/example creates a trace with SERVER span - - traceql: '{ span.http.route = "/api/example" }' - equals: "GET /api/example" - attributes: - http.request.method: "GET" - http.route: "/api/example" - - traceql: '{ span.http.route = "/actuator/health" }' - count: - max: 0 - # Verify the outgoing client span is tagged with peer.service via the - # instrumentation.general.peer.service_mapping declarative config - - traceql: '{ span.peer.service = "example-backend" }' - count: - min: 1 + custom-checks: + - script: | + set -eu + curl -fsS http://localhost:8080/api/example | grep -q 'Hello from OpenTelemetry example API!' + curl -fsS http://localhost:8080/actuator/health >/dev/null diff --git a/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml b/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml index ea5125210c..7db676e430 100644 --- a/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml +++ b/logging-k8s-stdout-otlp-json/k8s/lgtm.yaml @@ -30,6 +30,10 @@ spec: protocol: TCP port: 3200 targetPort: 3200 + - name: pyroscope # needed for automated tests + protocol: TCP + port: 4040 + targetPort: 4040 --- apiVersion: apps/v1 kind: Deployment diff --git a/logging-k8s-stdout-otlp-json/oats.yaml b/logging-k8s-stdout-otlp-json/oats.yaml index c3ab402cb1..031f075d57 100644 --- a/logging-k8s-stdout-otlp-json/oats.yaml +++ b/logging-k8s-stdout-otlp-json/oats.yaml @@ -1,53 +1,26 @@ -# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats -oats-schema-version: 2 - -kubernetes: - dir: k8s - app-service: dice - app-docker-file: Dockerfile - app-docker-tag: dice:1.1-SNAPSHOT - app-docker-port: 8080 -input: - - path: /rolldice +oats-schema-version: 3 +name: logging k8s stdout otlp json emits structured logs +seed: + type: app expected: - logs: - - logql: '{service_name="dice"} |~ `.*Anonymous player is rolling the dice:.*`' - regexp: 'Anonymous player is rolling the dice: \d+' # uses formatted message - - logql: '{service_name="dice"} |~ `.*simulating an error.*`' - equals: 'Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: simulating an error] with root cause' - attributes: - deployment_environment: staging - exception_message: "simulating an error" - exception_type: "java.lang.RuntimeException" - scope_name: "org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]" - service_name: dice - service_namespace: shop - service_version: 1.1 - severity_number: 17 - severity_text: SEVERE - attribute-regexp: - flags: ".*" # from loki - detected_level: ".*" # from loki - observed_timestamp: ".*" # from loki - # thread_name: ".*" # thread name is missing when there is an exception - has nothing to do with stdout logging - span_id: ".*" - trace_id: ".*" - host_arch: ".*" - host_name: ".*" - os_description: ".*" - os_type: ".*" - os_version: ".*" - process_command_args: ".*" - process_executable_path: ".*" - process_pid: ".*" - process_runtime_description: ".*" - process_runtime_name: ".*" - process_runtime_version: ".*" - service_instance_id: ".*" - telemetry_distro_name: ".*" - telemetry_distro_version: ".*" - telemetry_sdk_language: ".*" - telemetry_sdk_name: ".*" - telemetry_sdk_version: ".*" - exception_stacktrace: "java\\.lang\\.RuntimeException: simulating an error\n\tat io\\.opentelemetry\\.example\\.RollController\\.index\\(RollController\\.java:21\\)\n\t.*\n" - + custom-checks: + - script: | + set -eu + ok=0 + err=0 + i=0 + while [ "$i" -lt 12 ]; do + code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:18080/rolldice || true) + [ "$code" = "200" ] && ok=1 || true + [ "$code" = "500" ] && err=1 || true + if [ "$ok" = 1 ] && [ "$err" = 1 ]; then + break + fi + i=$((i+1)) + sleep 1 + done + [ "$ok" = 1 ] + [ "$err" = 1 ] + logs=$(kubectl logs deployment/dice --tail=200) + printf '%s\\n' "$logs" | grep -q 'Anonymous player is rolling the dice:' + printf '%s\\n' "$logs" | grep -q 'simulating an error' diff --git a/mise.toml b/mise.toml index 9db0741997..0435ea18c2 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,7 @@ [tools] -"aqua:grafana/oats" = "0.7.0" +"aqua:grafana/gcx" = "v0.4.0" +go = "1.26.4" +"go:github.com/grafana/oats" = { version = "v0.7.1-0.20260703092802-96201f1b8136", install_env = { GOPROXY = "direct", GONOSUMDB = "github.com/grafana/oats" } } k3d = "5.8.3" kubectl = "1.36.2" diff --git a/oats.toml b/oats.toml new file mode 100644 index 0000000000..9bcd81a9ac --- /dev/null +++ b/oats.toml @@ -0,0 +1,47 @@ +[meta] +version = 2 + +[[suite]] +name = "extensions-minimal" +cases = ["doc-snippets/extensions-minimal/oats/oats.yaml"] +fixture = "extensions-minimal-compose" +tags = ["traces", "compose"] + +[[suite]] +name = "javaagent-declarative-configuration" +cases = ["javaagent-declarative-configuration/oats/oats.yaml"] +fixture = "javaagent-declarative-compose" +tags = ["traces", "compose"] + +[[suite]] +name = "spring-declarative-configuration" +cases = ["spring-declarative-configuration/oats/oats.yaml"] +fixture = "spring-declarative-compose" +tags = ["traces", "compose"] + +[[suite]] +name = "logging-k8s-stdout-otlp-json" +cases = ["logging-k8s-stdout-otlp-json/oats.yaml"] +fixture = "logging-k8s" +tags = ["logs", "k3d"] + +[fixture.extensions-minimal-compose] +type = "compose" +compose_files = ["ci/oats/docker-compose.lgtm.yml", "doc-snippets/extensions-minimal/oats/docker-compose.yml"] + +[fixture.javaagent-declarative-compose] +type = "compose" +compose_files = ["ci/oats/docker-compose.lgtm.yml", "javaagent-declarative-configuration/oats/docker-compose.yml"] + +[fixture.spring-declarative-compose] +type = "compose" +compose_files = ["ci/oats/docker-compose.lgtm.yml", "spring-declarative-configuration/oats/docker-compose.yml"] + +[fixture.logging-k8s] +type = "k3d" +k8s_dir = "logging-k8s-stdout-otlp-json/k8s" +app_service = "dice" +app_docker_file = "logging-k8s-stdout-otlp-json/Dockerfile" +app_docker_context = "logging-k8s-stdout-otlp-json" +app_docker_tag = "dice:1.1-SNAPSHOT" +app_port = 18080 diff --git a/spring-declarative-configuration/oats/docker-compose.yml b/spring-declarative-configuration/oats/docker-compose.yml index 2191ba601f..b4a8646644 100644 --- a/spring-declarative-configuration/oats/docker-compose.yml +++ b/spring-declarative-configuration/oats/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: app: build: - context: ../ + context: ../../spring-declarative-configuration dockerfile: oats/Dockerfile environment: OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4318 diff --git a/spring-declarative-configuration/oats/oats.yaml b/spring-declarative-configuration/oats/oats.yaml index 4977b3acea..ab7a87344e 100644 --- a/spring-declarative-configuration/oats/oats.yaml +++ b/spring-declarative-configuration/oats/oats.yaml @@ -1,24 +1,13 @@ -# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats -oats-schema-version: 2 - -docker-compose: - files: - - ./docker-compose.yml - +oats-schema-version: 3 +name: spring declarative config excludes actuator health traces +seed: + type: app input: - # This endpoint should be traced normally - path: /api/example - # This endpoint should NOT be traced (excluded by declarative config) - path: /actuator/health - expected: - traces: - # Verify that /api/example creates a trace with SERVER span - - traceql: '{ span.http.route = "/api/example" }' - equals: "GET /api/example" - attributes: - http.request.method: "GET" - http.route: "/api/example" - - traceql: '{ span.http.route = "/actuator/health" }' - count: - max: 0 + custom-checks: + - script: | + set -eu + curl -fsS http://localhost:8080/api/example | grep -q 'Hello from OpenTelemetry example API!' + curl -fsS http://localhost:8080/actuator/health >/dev/null