Bindy DNS Operator - Testing Guide¶
Complete guide for testing the Bindy DNS Operator, including unit tests and integration tests with Kind (Kubernetes in Docker).
Quick Start¶
# Unit tests (fast, no Kubernetes required)
make test
# Integration tests (automated with Kind cluster)
make kind-integration-test
# View results
# Unit: 62 tests passing
# Integration: All 8 DNS record types + infrastructure tests
Table of Contents¶
Test Overview¶
Test Results¶
Unit Tests: 62 PASSING ✅
Integration Tests: Automated with Kind - Kubernetes connectivity ✅ - CRD verification ✅ - All 8 DNS record types ✅ - Resource lifecycle ✅
Test Structure¶
bindy/
├── crates/
│ ├── bindy-api/src/
│ │ └── crd_tests.rs # CRD structure tests
│ └── bindy/
│ ├── src/**/*_tests.rs # Unit tests, one file per module
│ └── tests/
│ ├── simple_integration.rs # Rust integration tests
│ └── common/mod.rs # Shared test utilities
├── tests/
│ └── integration_test.sh # Full integration test suite
└── deploy/
├── kind-deploy.sh # Deploy to Kind cluster
├── kind-test.sh # Basic functional tests
└── kind-cleanup.sh # Cleanup Kind cluster
Unit Tests¶
Unit tests run locally without Kubernetes (< 1 second).
Running Unit Tests¶
# All unit tests
make test
# or
cargo test
# Specific module
cargo test crd_tests::
cargo test bind9::tests::
# With output
cargo test -- --nocapture
Unit Test Coverage (62 tests)¶
CRD Tests (28 tests)¶
- Label selectors and matching
- SOA record structure
- DNSZone specs (primary/secondary)
- All DNS record types (A, AAAA, CNAME, MX, TXT, NS, SRV, CAA)
- Bind9Instance configurations
- DNSSEC settings
Bind9Manager Tests (34 tests)¶
- Zone file creation
- Email formatting for DNS
- All DNS record types (with/without TTL)
- Secondary zone configuration
- Zone lifecycle (create, exists, delete)
- Edge cases and workflows
Integration Tests¶
Integration tests run against Kind (Kubernetes in Docker) clusters.
Prerequisites¶
# Docker
docker --version # 20.10+
# Kind
kind --version # 0.20.0+
brew install kind # macOS
# kubectl
kubectl version --client # 1.24+
Running Integration Tests¶
Full Integration Suite (Recommended)¶
This automatically: 1. Creates Kind cluster (if needed) 2. Builds and deploys operator 3. Runs all integration tests 4. Cleans up test resources
Step-by-Step¶
# 1. Deploy to Kind
make kind-deploy
# 2. Run functional tests
make kind-test
# 3. Run comprehensive integration tests
make kind-integration-test
# 4. View logs
make kind-logs
# 5. Cleanup
make kind-cleanup
Kind credentials live in their own kubeconfig¶
Every kind-*, e2e-* and kind-based test target runs with KUBECONFIG
set to ~/.kube/kind-bindy.yaml (override with KIND_KUBECONFIG=...),
whatever your shell exports. kind create cluster merges its credentials
into the file KUBECONFIG points at and makes them the current context, so
without this a kind run could take over a real cluster's kubeconfig. To use
the kind cluster yourself:
eval "$(make -s kind-kubeconfig)" # export KUBECONFIG=~/.kube/kind-bindy.yaml
kubectl get pods -n bindy-system
In CI (CI=true) the default kubeconfig is used, since the runner has no
other.
Integration Test Coverage¶
Rust Integration Tests
- test_kubernetes_connectivity - Cluster access
- test_crds_installed - CRD verification
- test_create_and_cleanup_namespace - Namespace lifecycle
Full Integration Suite (integration_test.sh)
- Bind9Instance creation
- DNSZone creation
- A Record (IPv4)
- AAAA Record (IPv6)
- CNAME Record
- MX Record
- TXT Record
- NS Record
- SRV Record
- CAA Record
Expected Output¶
🧪 Running Bindy Integration Tests
✅ Using existing cluster 'bindy-test'
1️⃣ Running Rust integration tests...
test test_kubernetes_connectivity ... ok
test test_crds_installed ... ok
test test_create_and_cleanup_namespace ... ok
2️⃣ Running functional tests with kubectl...
Testing Bind9Instance creation...
Testing DNSZone creation...
Testing all DNS record types...
3️⃣ Verifying resources...
✓ Bind9Instance created
✓ DNSZone created
✓ arecord created
✓ aaaarecord created
✓ cnamerecord created
✓ mxrecord created
✓ txtrecord created
✓ nsrecord created
✓ srvrecord created
✓ caarecord created
✅ All integration tests passed!
Chaos Suite¶
make e2e-chaos (the CI job, about four to five minutes) deletes each primary
of a two-primary, one-secondary topology in turn, then the secondary, then
every BIND9 pod at once, and checks after every step that each BIND9 pod
serves the exact expected records, that the zone transfer peers name exactly
the current pods, that every status is truthful, and that the operator goes
quiet. make e2e-chaos-full runs fifteen kinds of failure (operator, pod,
container, rollout, churn) twice and takes hours. See
Chaos Testing.
Makefile Targets¶
Test Targets¶
make test # Run unit tests
make test-lib # Library tests only
make test-integration # Rust integration tests
make test-all # Unit + Rust integration tests
make test-cov # Coverage report (HTML)
make test-cov-view # Generate and open coverage
Kind Targets¶
make kind-create # Create Kind cluster (Queen Bee / operator cluster)
make kind-deploy # Deploy operator
make kind-test # Basic functional tests
make kind-integration-test # Full integration suite
make kind-logs # View operator logs
make kind-cleanup # Delete cluster
Multi-Cluster Scout Testing¶
A second Kind cluster (bindy-scout) can be created to test Scout in multi-cluster mode: Scout running on the child cluster, writing ARecord CRs back to the Queen Bee cluster.
# Create and install Scout on the child cluster
make kind-create-scout IMAGE_TAG=latest-dev
# Tear it down when done
make kind-scout-cleanup
kind-create-scout performs all setup steps automatically:
- Creates a single-node Kind cluster named
bindy-scout - Loads the image into the cluster
- Creates the
bindy-systemnamespace - Installs all Bindy CRDs
- Applies Scout RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding, Role, RoleBinding)
- Deploys the Scout controller
- Waits for the deployment to become available
Connecting Scout to the Queen Bee cluster (multi-cluster mode)
After both clusters are running, use bindy bootstrap mc to generate credentials on the Queen Bee cluster and apply them to the Scout cluster:
# Switch to the Queen Bee cluster
kubectl config use-context kind-bindy-test
# Generate credentials, registered for the Scout cluster's name and zone
# (deploy/scout/deployment.yaml names it test-cluster.example.com)
bindy bootstrap mc -o scout-kubeconfig.yaml \
--cluster-name test-cluster.example.com \
--allowed-zones test-cluster.example.com
kubectl --context kind-bindy-scout apply -f scout-kubeconfig.yaml
rm scout-kubeconfig.yaml
# Bind registered accounts on the Queen Bee cluster (ADR-0025)
make admission-policies-install
# Then patch the Scout Deployment to enable remote mode and its allowlists
kubectl --context kind-bindy-scout set env deployment/bindy-scout \
BINDY_SCOUT_REMOTE_SECRET=bindy-scout-remote-kubeconfig \
BINDY_SCOUT_ALLOWED_ZONES=test-cluster.example.com \
BINDY_SCOUT_ALLOWED_IP_RANGES=0.0.0.0/0 \
-n bindy-system
0.0.0.0/0 keeps every kind address publishable; narrow it to test the
address check. To test the bindy-side binding without a second Scout, write
an ARecord as the registered account for another zone and expect a denial:
kubectl apply --dry-run=server --as=system:serviceaccount:bindy-system:bindy-scout-remote -f deploy/admission-policies/tests/reject-arecord-scout-foreign-zone.yaml
(the fixture's labels name edge-foo, so it is refused for the cluster name
as well as the zone). The policy's own fixtures, including the ones that
impersonate a registered account, run with make admission-policies-test,
which applies deploy/admission-policies/tests/setup/ first.
See the Scout Multi-Cluster Guide for the full architecture and configuration reference.
Other Targets¶
make lint # Run clippy and fmt check
make format # Format code
make build # Build release binary
make docker-build # Build Docker image
Troubleshooting¶
Unit Tests¶
Tests fail to compile
Specific test fails
Integration Tests¶
"Cluster not found"
"Operator not ready"
# Check status
kubectl get pods -n bindy-system
# View logs
kubectl logs -n bindy-system -l app=bindy
# Redeploy
./deploy/kind-deploy.sh
"CRDs not installed"
# Check CRDs
kubectl get crds | grep bindy.firestoned.io
# Install
kubectl apply --server-side -f deploy/operator/crds/
Resource creation fails
# Operator logs
kubectl logs -n bindy-system -l app=bindy --tail=50
# Resource status
kubectl describe bind9instance <name> -n bindy-system
# Events
kubectl get events -n bindy-system --sort-by='.lastTimestamp'
Manual Cleanup¶
# Delete test resources
kubectl delete bind9instances,dnszones,arecords,aaaarecords,cnamerecords,mxrecords,txtrecords,nsrecords,srvrecords,caarecords,ptrrecords --all -n bindy-system
# Delete cluster
kind delete cluster --name bindy-test
# Clean build
cargo clean
CI/CD Integration¶
GitHub Actions¶
Current PR flow (.github/workflows/build.yaml, pull_request jobs):
- Lint (formatting, clippy)
- Test (unit tests)
- Build (stable, beta)
- Docker (build and push to ghcr.io)
- Security audit
- Coverage
Add Integration Tests¶
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install Kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
- name: Run Integration Tests
run: |
chmod +x tests/integration_test.sh
./tests/integration_test.sh
Test Development¶
Writing Unit Tests¶
Add to crates/bindy-api/src/crd_tests.rs or crates/bindy-controller-*/src/*_tests.rs:
#[test]
fn test_my_feature() {
// Arrange
let (_temp_dir, manager) = create_test_manager();
// Act
let result = manager.my_operation();
// Assert
assert!(result.is_ok());
}
Writing Integration Tests¶
Add to crates/bindy/tests/simple_integration.rs:
#[tokio::test]
#[ignore] // Always mark as ignored
async fn test_my_scenario() {
let client = match get_kube_client_or_skip().await {
Some(c) => c,
None => return, // Skip if no cluster
};
// Test code here
}
Using Test Helpers¶
From crates/bindy/tests/common/mod.rs:
use common::*;
let client = setup_dns_test_environment("my-test-ns").await?;
create_bind9_instance(&client, "ns", "dns", None).await?;
wait_for_ready(Duration::from_secs(10)).await;
cleanup_test_namespace(&client, "ns").await?;
Performance Testing¶
Coverage¶
Load Testing¶
# Create many resources
for i in {1..100}; do
kubectl apply -f - <<EOF
apiVersion: bindy.firestoned.io/v1beta1
kind: ARecord
metadata:
name: test-${i}
namespace: bindy-system
spec:
zone: example.com
name: host-${i}
ipv4Addresses:
- "192.0.2.${i}"
EOF
done
# Monitor
kubectl top pod -n bindy-system
Best Practices¶
Unit Tests¶
- Test one thing at a time
- Fast (< 1s each)
- No external dependencies
- Descriptive names
Integration Tests¶
- Always use
#[ignore] - Check cluster connectivity first
- Unique namespaces
- Always cleanup
- Good error messages
General¶
- Run
cargo fmtbefore committing - Run
cargo clippyto catch issues - Keep tests updated
- Document complex scenarios
Additional Resources¶
- Rust Testing
- Kube-rs Examples
- Kind Docs
- BIND9 Docs
- TEST_SUMMARY.md - Quick reference
Support¶
- GitHub Issues: https://github.com/firestoned/bindy/issues
- Operator logs:
make kind-logs - Test with output:
cargo test -- --nocapture