Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ run-homelab-restore:
argo submit --from workflowtemplate/homelab-restore-drill \
-n {{ argo_ns }} --wait --log

# ── Service-catalog workload lanes ───────────────────────────────────────────

# Run the non-media (OpenPrinting/CUPS base) service-catalog lane (#81)
run-service-nonmedia:
argo submit --from workflowtemplate/homelab-nonmedia-service \
-n {{ argo_ns }} --wait --log

# ── Ghost maintenance ─────────────────────────────────────────────────────────

# Patch ghost OTel collector config to remove noisy process scraper (#117)
Expand Down
4 changes: 4 additions & 0 deletions RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Golden disks can be patched by workflow after key rotation; titan disk key refre
| VM stuck `Terminating` | KubeVirt controller race with launcher cleanup | Delete the `virt-launcher-*` pod and let reconciliation finish |
| `run-gnome-tests` pod fails at startup | Workflow template structure error, often misplaced `volumes:` | Fix the template in git and let ArgoCD reconcile it |
| WorkflowTemplate change appears ignored | Workflow was submitted before the new template was reconciled | Verify ArgoCD revision, wait or sync, then submit a new workflow |
| `PVC nonmedia-service-config not Bound` in service-catalog lane | No default StorageClass or provisioner not ready | Verify `kubectl get storageclass`; ensure the local-path provisioner pod is Running on ghost |
| `nonmedia-service` pod stuck `Pending` in lane tests | nodeSelector `ghost` doesn't match any Ready node | Check `kubectl get nodes` — exo-1 is ineligible; ghost must be Ready |
| Lane test suite skipped entirely | `TEST_NAMESPACE` env var unset or empty | Confirm the runner pod is launched from the `run-incluster-tests` template, not directly |
| Cleanup step hangs waiting for namespace deletion | Finalizers on PVCs blocking namespace GC | Run `kubectl get pvc -n <ns>` and remove stuck finalizers with `kubectl patch pvc … -p '{"metadata":{"finalizers":[]}}'` |

## Historical notes

Expand Down
49 changes: 49 additions & 0 deletions WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,55 @@ just run-dakota-build all # both variants sequentially

---

## Service-catalog workload lanes

In-cluster validation suites that deploy a fixture workload into a per-run namespace,
run pytest checks against the live Kubernetes objects, and clean up on exit.
Each lane is a self-contained `WorkflowTemplate` with the DAG:
`create-namespace → deploy-fixture → run-tests (onExit: cleanup)`.

All lanes delegate the test-runner pod to the shared `run-incluster-tests / run-pytest`
template, which clones the testing-lab repo and runs `pytest -q <suite-path>`.

### `homelab-nonmedia-service` (issue #81)

Validates the base homelab workload contract for network-only print services
(OpenPrinting/CUPS class, bluespeed#11). The fixture is a lightweight nginx stub
on port 631 (IPP-standard) so the contract can be proven without real CUPS hardware.

| Parameter | Default | Notes |
|---|---|---|
| `lane` | `nonmedia` | Fixed; used as `TEST_LANE` env var inside the runner pod. |
| `branch` | `main` | testing-lab git ref cloned by the runner. |

Suite path: `tests/service_catalog/nonmedia/test_nonmedia_lane.py` (15 tests; 2 explicit skips for USB and mDNS hardware — see issue #67).

```
just run-service-nonmedia # submit + stream logs, ~5–8 min
```

Out-of-scope (separate lanes):
- USB printer device hostPath passthrough → `homelab-print-device` (issue #67)
- avahi mDNS / LAN discovery → `homelab-print-device` (issue #67)
- GPU transcoding → `homelab-media-gpu` (issue #63)

### `bluefin-service-catalog-pipeline` (issue #81)

Top-level aggregation pipeline for the service-catalog suite. Routes to the
matching lane WorkflowTemplate based on the `lane` parameter.

| Parameter | Default | Valid values |
|---|---|---|
| `lane` | `nonmedia` | `nonmedia` (first lane; extend as new lanes are added) |
| `branch` | `main` | testing-lab git ref |

```
argo submit --from workflowtemplate/bluefin-service-catalog-pipeline \
-n argo -p lane=nonmedia --wait --log
```

---

## CronWorkflows

Lives in `manifests/`, applied via the `testing-lab-infra` ArgoCD app:
Expand Down
15 changes: 15 additions & 0 deletions argo/homelab-nonmedia-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: nonmedia-service-
namespace: argo
spec:
serviceAccountName: argo
workflowTemplateRef:
name: homelab-nonmedia-service
arguments:
parameters:
- name: lane
value: "nonmedia"
- name: branch
value: "main"
52 changes: 52 additions & 0 deletions argo/workflow-templates/bluefin-service-catalog-pipeline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: bluefin-service-catalog-pipeline
namespace: argo
labels:
app.kubernetes.io/component: service-catalog-pipeline
app.kubernetes.io/part-of: bluefin-test-suite
spec:
entrypoint: pipeline
serviceAccountName: argo
# ── parameters ──────────────────────────────────────────────────────────────
arguments:
parameters:
# Lane selector: controls which service-catalog workload is exercised.
# Valid values: nonmedia (media lanes are in separate templates)
- name: lane
value: "nonmedia"
# git ref the in-cluster runner clones when executing pytest suites.
- name: branch
value: "main"

# ── pipeline DAG ────────────────────────────────────────────────────────────
templates:
- name: pipeline
dag:
tasks:
# Route execution to the matching lane WorkflowTemplate.
# New lanes are added as additional when/templateRef entries here.
- name: nonmedia-lane
template: run-nonmedia-lane
when: "\"{{workflow.parameters.lane}}\" == \"nonmedia\""
arguments:
parameters:
- name: branch
value: "{{workflow.parameters.branch}}"

- name: run-nonmedia-lane
inputs:
parameters:
- name: branch
steps:
- - name: execute
templateRef:
name: homelab-nonmedia-service
template: pipeline
arguments:
parameters:
- name: lane
value: "nonmedia"
- name: branch
value: "{{inputs.parameters.branch}}"
182 changes: 182 additions & 0 deletions argo/workflow-templates/homelab-nonmedia-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: homelab-nonmedia-service
namespace: argo
labels:
app.kubernetes.io/component: homelab-nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
spec:
entrypoint: pipeline
onExit: cleanup
serviceAccountName: homelab-runner
arguments:
parameters:
- name: lane
value: "nonmedia"
- name: branch
value: "main"
templates:
- name: pipeline
dag:
tasks:
- name: create-namespace
template: create-namespace
arguments:
parameters:
- name: namespace
value: "nonmedia-service-{{workflow.uid}}"
- name: deploy-fixture
depends: "create-namespace.Succeeded"
template: deploy-fixture
arguments:
parameters:
- name: namespace
value: "nonmedia-service-{{workflow.uid}}"
- name: lane
value: "{{workflow.parameters.lane}}"
- name: run-tests
depends: "deploy-fixture.Succeeded"
templateRef:
name: run-incluster-tests
template: run-pytest
arguments:
parameters:
- name: namespace
value: "nonmedia-service-{{workflow.uid}}"
- name: suite-path
value: "service_catalog/nonmedia"
- name: lane
value: "{{workflow.parameters.lane}}"
- name: app-label
value: "app=nonmedia-service"
- name: service-name
value: "nonmedia-service"
- name: branch
value: "{{workflow.parameters.branch}}"

- name: create-namespace
inputs:
parameters:
- name: namespace
resource:
action: create
manifest: |
apiVersion: v1
kind: Namespace
metadata:
name: "{{inputs.parameters.namespace}}"
labels:
app.kubernetes.io/part-of: bluefin-test-suite
bluefin.io/lane: nonmedia

- name: deploy-fixture
inputs:
parameters:
- name: namespace
- name: lane
script:
image: cgr.dev/chainguard/kubectl:latest-dev
command: [bash, -c]
source: |
set -euo pipefail
NS="{{inputs.parameters.namespace}}"

# PVC must be applied before the Deployment so the volume mount
# is satisfiable when the first pod is scheduled.
kubectl apply -n "${NS}" -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nonmedia-service-config
labels:
app: nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
EOF

kubectl apply -n "${NS}" -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nonmedia-service
labels:
app: nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
bluefin.io/lane: "{{inputs.parameters.lane}}"
bluefin.io/workload-class: nonmedia-service
spec:
replicas: 1
selector:
matchLabels:
app: nonmedia-service
template:
metadata:
labels:
app: nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
bluefin.io/lane: "{{inputs.parameters.lane}}"
bluefin.io/workload-class: nonmedia-service
spec:
nodeSelector:
kubernetes.io/hostname: ghost
containers:
- name: print-server
image: docker.io/library/nginx:1.27.5-alpine
ports:
- containerPort: 631
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "UTC"
volumeMounts:
- name: config
mountPath: /config
command:
- sh
- -c
- |
sed -i 's/listen\s*80;/listen 631;/g' /etc/nginx/conf.d/default.conf
exec nginx -g 'daemon off;'
volumes:
- name: config
persistentVolumeClaim:
claimName: nonmedia-service-config
---
apiVersion: v1
kind: Service
metadata:
name: nonmedia-service
labels:
app: nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
bluefin.io/lane: "{{inputs.parameters.lane}}"
spec:
selector:
app: nonmedia-service
ports:
- name: ipp
port: 631
targetPort: 631
EOF

kubectl rollout status deployment/nonmedia-service -n "${NS}" --timeout=300s >&2

- name: cleanup
script:
image: cgr.dev/chainguard/kubectl:latest-dev
command: [bash, -c]
source: |
set -euo pipefail
NS="nonmedia-service-{{workflow.uid}}"
kubectl delete namespace "${NS}" --ignore-not-found=true >&2 || true
timeout 180 bash -c "until ! kubectl get namespace \"${NS}\" >/dev/null 2>&1; do sleep 5; done" >&2 || true
echo "cleanup-complete"
Empty file.
16 changes: 16 additions & 0 deletions tests/service_catalog/nonmedia/fixtures/nonmedia-pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
# PVC fixture for the non-media service lane (config volume).
# Applied first so the Deployment can reference it immediately.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nonmedia-service-config
labels:
app: nonmedia-service
app.kubernetes.io/part-of: bluefin-test-suite
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Loading