Skip to content

Commit bb1b7aa

Browse files
authored
Merge pull request #1 from SentinelOps-CI/paper-implementation
Paper implementation
2 parents fac8974 + 6410f31 commit bb1b7aa

28 files changed

Lines changed: 11307 additions & 717 deletions

.github/workflows/testbed-paper-faithful.yml

Lines changed: 502 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
name: Testbed Report Generation
2+
3+
on:
4+
schedule:
5+
# Run every Sunday at 2 AM UTC
6+
- cron: "0 2 * * 0"
7+
workflow_dispatch:
8+
inputs:
9+
force_regenerate:
10+
description: "Force regenerate all reports"
11+
required: false
12+
default: false
13+
type: boolean
14+
include_screenshots:
15+
description: "Include Grafana screenshots"
16+
required: false
17+
default: true
18+
type: boolean
19+
20+
env:
21+
PYTHON_VERSION: "3.11"
22+
REPORT_OUTPUT_DIR: "testbed/reports"
23+
VALIDATION_STRICT: "true"
24+
25+
jobs:
26+
generate-reports:
27+
name: Generate Testbed Reports
28+
runs-on: ubuntu-latest
29+
30+
services:
31+
prometheus:
32+
image: prom/prometheus:latest
33+
ports:
34+
- 9090:9090
35+
volumes:
36+
- ./testbed/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
37+
options: >-
38+
--config.file=/etc/prometheus/prometheus.yml
39+
--storage.tsdb.path=/prometheus
40+
--web.console.libraries=/etc/prometheus/console_libraries
41+
--web.console.templates=/etc/prometheus/consoles
42+
--storage.tsdb.retention.time=200h
43+
--web.enable-lifecycle
44+
45+
grafana:
46+
image: grafana/grafana:latest
47+
ports:
48+
- 3000:3000
49+
env:
50+
GF_SECURITY_ADMIN_PASSWORD: admin
51+
GF_USERS_ALLOW_SIGN_UP: false
52+
volumes:
53+
- ./testbed/grafana/provisioning:/etc/grafana/provisioning
54+
- grafana-storage:/var/lib/grafana
55+
56+
ledger:
57+
image: postgres:15
58+
ports:
59+
- 5432:5432
60+
env:
61+
POSTGRES_DB: testbed
62+
POSTGRES_USER: testbed
63+
POSTGRES_PASSWORD: testbed
64+
volumes:
65+
- postgres-data:/var/lib/postgresql/data
66+
- ./testbed/runtime/ledger/init.sql:/docker-entrypoint-initdb.d/init.sql
67+
68+
steps:
69+
- name: Checkout code
70+
uses: actions/checkout@v4
71+
with:
72+
fetch-depth: 0
73+
token: ${{ secrets.GITHUB_TOKEN }}
74+
75+
- name: Set up Python
76+
uses: actions/setup-python@v4
77+
with:
78+
python-version: ${{ env.PYTHON_VERSION }}
79+
cache: "pip"
80+
81+
- name: Install system dependencies
82+
run: |
83+
sudo apt-get update
84+
sudo apt-get install -y \
85+
libcairo2-dev \
86+
libpango1.0-dev \
87+
libgdk-pixbuf2.0-dev \
88+
libffi-dev \
89+
shared-mime-info \
90+
libpq-dev
91+
92+
- name: Install Python dependencies
93+
run: |
94+
pip install --upgrade pip
95+
pip install -r testbed/tools/reporter/requirements.txt
96+
pip install -r requirements.txt
97+
98+
- name: Wait for services
99+
run: |
100+
echo "Waiting for services to be ready..."
101+
timeout 300 bash -c 'until curl -s http://localhost:9090/-/healthy; do sleep 5; done'
102+
timeout 300 bash -c 'until curl -s http://localhost:3000/api/health; do sleep 5; done'
103+
timeout 300 bash -c 'until pg_isready -h localhost -p 5432 -U testbed; do sleep 5; done'
104+
105+
- name: Create sample data
106+
run: |
107+
python testbed/data/generator.py --output testbed/data/sample_data.json
108+
python testbed/chaos/chaos_runner.py --config testbed/chaos/chaos_config.yaml
109+
110+
- name: Generate comprehensive report
111+
run: |
112+
python testbed/tools/reporter/generate_testbed_report.py \
113+
--prometheus-url http://localhost:9090 \
114+
--ledger-url http://localhost:5432 \
115+
--grafana-url http://localhost:3000 \
116+
--grafana-user admin \
117+
--grafana-password admin \
118+
--output-dir ${{ env.REPORT_OUTPUT_DIR }} \
119+
--format both \
120+
--include-art \
121+
--include-certs \
122+
--include-screenshots \
123+
--validation-strict
124+
env:
125+
TESTBED_ID: ${{ github.run_id }}
126+
127+
- name: Validate report artifacts
128+
run: |
129+
echo "Validating report artifacts..."
130+
131+
# Check if reports were generated
132+
if [ ! -f "${{ env.REPORT_OUTPUT_DIR }}"/*.pdf ]; then
133+
echo "❌ PDF report not found"
134+
exit 1
135+
fi
136+
137+
if [ ! -f "${{ env.REPORT_OUTPUT_DIR }}"/*.html ]; then
138+
echo "❌ HTML report not found"
139+
exit 1
140+
fi
141+
142+
if [ ! -f "${{ env.REPORT_OUTPUT_DIR }}"/*.json ]; then
143+
echo "❌ JSON report not found"
144+
exit 1
145+
fi
146+
147+
# Validate JSON schema
148+
python -c "
149+
import json
150+
import jsonschema
151+
152+
# Load schema
153+
schema = {
154+
'type': 'object',
155+
'required': ['metadata', 'metrics', 'validation'],
156+
'properties': {
157+
'metadata': {'type': 'object'},
158+
'metrics': {'type': 'object'},
159+
'validation': {'type': 'object'}
160+
}
161+
}
162+
163+
# Load and validate report
164+
with open('${{ env.REPORT_OUTPUT_DIR }}' + '/' + [f for f in os.listdir('${{ env.REPORT_OUTPUT_DIR }}') if f.endswith('.json')][0], 'r') as f:
165+
report = json.load(f)
166+
167+
jsonschema.validate(instance=report, schema=schema)
168+
print('✅ JSON schema validation passed')
169+
"
170+
171+
# Check validation results
172+
python -c "
173+
import json
174+
import os
175+
176+
report_file = [f for f in os.listdir('${{ env.REPORT_OUTPUT_DIR }}') if f.endswith('.json')][0]
177+
with open(os.path.join('${{ env.REPORT_OUTPUT_DIR }}', report_file), 'r') as f:
178+
report = json.load(f)
179+
180+
validation = report.get('validation', {})
181+
182+
if not validation.get('artifacts_present', False):
183+
print('❌ Missing artifacts detected')
184+
print('Missing:', validation.get('missing_artifacts', []))
185+
exit(1)
186+
187+
if not validation.get('schema_valid', False):
188+
print('❌ Schema validation failed')
189+
print('Errors:', validation.get('validation_errors', []))
190+
exit(1)
191+
192+
print('✅ All validation checks passed')
193+
"
194+
195+
echo "✅ Report validation completed successfully"
196+
197+
- name: Upload reports as artifacts
198+
uses: actions/upload-artifact@v4
199+
with:
200+
name: testbed-reports-${{ github.run_number }}
201+
path: ${{ env.REPORT_OUTPUT_DIR }}/*
202+
retention-days: 30
203+
204+
- name: Upload reports to releases
205+
if: github.event_name == 'workflow_dispatch'
206+
uses: actions/upload-artifact@v4
207+
with:
208+
name: testbed-reports-release
209+
path: ${{ env.REPORT_OUTPUT_DIR }}/*
210+
retention-days: 90
211+
212+
- name: Notify on failure
213+
if: failure()
214+
uses: 8398a7/action-slack@v3
215+
with:
216+
status: failure
217+
channel: "#testbed-alerts"
218+
text: "Testbed report generation failed! Check the workflow for details."
219+
env:
220+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
221+
222+
weekly-summary:
223+
name: Weekly Report Summary
224+
runs-on: ubuntu-latest
225+
needs: generate-reports
226+
if: always()
227+
228+
steps:
229+
- name: Checkout code
230+
uses: actions/checkout@v4
231+
232+
- name: Download reports
233+
uses: actions/download-artifact@v4
234+
with:
235+
name: testbed-reports-${{ needs.generate-reports.outputs.run_number || github.run_number }}
236+
237+
- name: Generate summary
238+
run: |
239+
echo "📊 Weekly Testbed Report Summary" > summary.md
240+
echo "Generated: $(date)" >> summary.md
241+
echo "" >> summary.md
242+
243+
if [ -f "*.json" ]; then
244+
echo "✅ Reports generated successfully" >> summary.md
245+
echo "- PDF: Available" >> summary.md
246+
echo "- HTML: Available" >> summary.md
247+
echo "- JSON: Available" >> summary.md
248+
else
249+
echo "❌ Report generation failed" >> summary.md
250+
fi
251+
252+
echo "" >> summary.md
253+
echo "🔗 [View Workflow Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> summary.md
254+
255+
- name: Comment on issue
256+
uses: actions/github-script@v7
257+
with:
258+
script: |
259+
const fs = require('fs');
260+
const summary = fs.readFileSync('summary.md', 'utf8');
261+
262+
// Find or create weekly summary issue
263+
const { data: issues } = await github.rest.issues.listForRepo({
264+
owner: context.repo.owner,
265+
repo: context.repo.repo,
266+
labels: ['weekly-summary'],
267+
state: 'open'
268+
});
269+
270+
let issue;
271+
if (issues.length === 0) {
272+
// Create new issue
273+
const { data: newIssue } = await github.rest.issues.create({
274+
owner: context.repo.owner,
275+
repo: context.repo.repo,
276+
title: `Weekly Testbed Report - ${new Date().toISOString().split('T')[0]}`,
277+
body: summary,
278+
labels: ['weekly-summary', 'automated']
279+
});
280+
issue = newIssue;
281+
} else {
282+
// Update existing issue
283+
issue = issues[0];
284+
await github.rest.issues.update({
285+
owner: context.repo.owner,
286+
repo: context.repo.repo,
287+
issue_number: issue.number,
288+
body: summary
289+
});
290+
}
291+
292+
console.log(`Updated issue #${issue.number}`)

0 commit comments

Comments
 (0)