From 03fc57b31daca5d1dfb8987f4a11349b49dcfcd3 Mon Sep 17 00:00:00 2001 From: Roberto Nascimento <91574266+Roberton003@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:06:30 -0300 Subject: [PATCH 1/3] ci: job e2e que sobe o compose e prova que os traces chegam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O job `test` roda contra um Postgres avulso e nunca sobe o compose: nada no CI cobria Dockerfile, entrypoint, migrate no boot ou a cadeia de observabilidade. A migracao do Jaeger para v2 foi validada so na minha maquina — este job passa isso para o CI. O passo central consulta a API do Jaeger e exige ao menos um trace do servico labtelemetry. Sem ele, uma quebra no exporter OTel ou no receptor OTLP passa despercebida indefinidamente. Usa as portas alternativas via variaveis de override, o que de quebra exercita esse mecanismo do compose. Passos validados localmente antes do push, incluindo o caminho de falha: com o container do Jaeger parado, o passo de traces sai com exit 1. --- .github/workflows/ci.yml | 89 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a04c38..ea8db71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,3 +80,92 @@ jobs: working-directory: ./labtelemetry env: DEBUG: "False" + + # O job `test` roda contra um Postgres avulso e nunca sobe o compose, entao + # nada nele cobre Docker, entrypoint, migrate no boot ou o caminho de + # observabilidade. Este job exercita o stack como um usuario o executa. + e2e: + name: e2e (compose + traces) + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + # Portas alternativas: o runner tem servicos proprios e, de quebra, + # isto exercita as variaveis de override do compose. + POSTGRES_PORT: "55432" + OTLP_GRPC_PORT: "54317" + OTLP_HTTP_PORT: "54318" + JAEGER_UI_PORT: "56686" + steps: + - uses: actions/checkout@v4 + + - name: Prepare .env + run: cp .env.example .env + + - name: Start stack + run: docker compose up -d --build + + - name: Wait for dashboard + run: | + for i in $(seq 1 60); do + code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/ || true) + if [ "$code" = "200" ]; then + echo "dashboard respondeu 200 apos ~$((i * 2))s" + exit 0 + fi + sleep 2 + done + echo "::error::dashboard nunca respondeu 200" + exit 1 + + - name: Ingest telemetry through the container + run: | + docker compose exec -T app \ + python manage.py ingest_telemetry --source simulator --once --sim-count 3 + + - name: API returns persisted data + run: | + curl -sS http://127.0.0.1:8000/api/summary/ | tee /tmp/summary.json + python3 - <<'PY' + import json + d = json.load(open("/tmp/summary.json")) + assert d["total_sensors"] > 0, d + assert d["total_readings"] > 0, d + print("summary ok:", d) + PY + + - name: Generate traced requests + run: | + for _ in $(seq 1 5); do + curl -sS -o /dev/null http://127.0.0.1:8000/api/summary/ + done + + # O ponto do job: prova que a cadeia OTel -> OTLP -> Jaeger esta viva. + # Sem isto, uma quebra no exporter ou no receptor passa despercebida. + - name: Traces reach Jaeger + run: | + for i in $(seq 1 45); do + if curl -s http://127.0.0.1:56686/api/services | grep -q labtelemetry; then + curl -s "http://127.0.0.1:56686/api/traces?service=labtelemetry&limit=5" \ + > /tmp/traces.json + if python3 -c " + import json, sys + data = json.load(open('/tmp/traces.json')).get('data') or [] + ops = {s['operationName'] for t in data for s in t['spans']} + print(f'{len(data)} traces, operacoes: {sorted(ops)}') + sys.exit(0 if data else 1) + "; then + exit 0 + fi + fi + sleep 2 + done + echo "::error::nenhum trace do servico labtelemetry chegou ao Jaeger" + exit 1 + + - name: Dump logs on failure + if: failure() + run: docker compose logs --no-color --tail=100 + + - name: Tear down + if: always() + run: docker compose down -v From 28efb6407bfa45d29e3c44a40d8aeab45ed7cc2e Mon Sep 17 00:00:00 2001 From: Roberto Nascimento <91574266+Roberton003@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:14:00 -0300 Subject: [PATCH 2/3] fix(docker): entrypoint sem bit de execucao quebrava todo clone novo --- Dockerfile | 7 +++++++ docker-entrypoint.sh | 0 scripts/publish_wiki.sh | 0 3 files changed, 7 insertions(+) mode change 100644 => 100755 docker-entrypoint.sh mode change 100644 => 100755 scripts/publish_wiki.sh diff --git a/Dockerfile b/Dockerfile index 3a9c077..d27a37a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,13 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . + +# O bit de execucao do entrypoint esta registrado no git, mas nem todo caminho +# ate aqui o preserva (download em zip, checkout no Windows, `COPY` a partir de +# um contexto reconstruido). Sem isto o container morre no boot com +# "permission denied" antes de rodar uma linha sequer. +RUN chmod +x /app/docker-entrypoint.sh + WORKDIR /app/labtelemetry EXPOSE 8000 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh old mode 100644 new mode 100755 diff --git a/scripts/publish_wiki.sh b/scripts/publish_wiki.sh old mode 100644 new mode 100755 From 9e68a0b98b9b45765090cd39c9bdc643357e026e Mon Sep 17 00:00:00 2001 From: Roberto Nascimento <91574266+Roberton003@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:20:52 -0300 Subject: [PATCH 3/3] ci: exige span HTTP no gate de traces, nao um trace qualquer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Na primeira execucao verde o job passou vendo apenas spans `SELECT`: os do psycopg sao exportados antes dos do Django, e a assercao original aceitava qualquer trace. Uma quebra na instrumentacao HTTP teria passado. Agora exige ao menos um span `GET ...` de servidor. O loop de retry ja existente absorve a latencia do BatchSpanProcessor — a espera aumenta um pouco, a assercao nao encurta. --- .github/workflows/ci.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea8db71..a9b471d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,25 +141,31 @@ jobs: # O ponto do job: prova que a cadeia OTel -> OTLP -> Jaeger esta viva. # Sem isto, uma quebra no exporter ou no receptor passa despercebida. + # + # Exige um span HTTP de servidor (`GET ...`), nao um trace qualquer: os + # spans do psycopg costumam ser exportados primeiro, e aceitar so eles + # deixaria passar uma quebra na instrumentacao do Django. O loop tolera + # a latencia do BatchSpanProcessor em vez de encurtar a assercao. - name: Traces reach Jaeger run: | for i in $(seq 1 45); do if curl -s http://127.0.0.1:56686/api/services | grep -q labtelemetry; then - curl -s "http://127.0.0.1:56686/api/traces?service=labtelemetry&limit=5" \ + curl -s "http://127.0.0.1:56686/api/traces?service=labtelemetry&limit=20" \ > /tmp/traces.json if python3 -c " import json, sys data = json.load(open('/tmp/traces.json')).get('data') or [] - ops = {s['operationName'] for t in data for s in t['spans']} - print(f'{len(data)} traces, operacoes: {sorted(ops)}') - sys.exit(0 if data else 1) + ops = sorted({s['operationName'] for t in data for s in t['spans']}) + http = [o for o in ops if o.startswith('GET ')] + print(f'{len(data)} traces, operacoes: {ops}') + sys.exit(0 if data and http else 1) "; then exit 0 fi fi sleep 2 done - echo "::error::nenhum trace do servico labtelemetry chegou ao Jaeger" + echo "::error::nao chegou nenhum span HTTP do servico labtelemetry ao Jaeger" exit 1 - name: Dump logs on failure