ecdba833d9
New 'orca doctor ingress' command: verifies podman orca-traefik
container running, nft DNAT+SNAT, /etc/traefik/dynamic exists,
step-ca root CA present.
UAT signoff script: replaced assertion 36 (systemd → podman
container), added assertions 40-46 (nft table, DNAT, SNAT, dynamic
dir, step-ca CA, traefik.yml, doctor ingress pass).
docs/ingress.md: R-024 podman traefik section — three topologies,
container config, nft ruleset, doctor ingress, Dockerfile.traefik.
TLS model updated (drop certResolver, tls:{} for v0.14, mTLS v0.15).
ARCHITECTURE.md: v0.14 deltas section — R-024, three topologies,
nft emitter changes, TLS model, migration 0009, new CLI.
Integration tests (tests/ingress_bootstrap_test.go): nft postrouting
+ DNATTarget, priority -10, traefik TLS model (tls:{} no
certResolver), image ref resolution, floating-IP LXC provisioning
commands (pct create with hwaddr/ip/gw/features), MAC generation.
---ci---
project: orca
phase: 7
milestone: v0.14
status: execute
---/ci---
164 lines
5.0 KiB
Go
164 lines
5.0 KiB
Go
package tests
|
|
|
|
import (
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/emitter"
|
|
"git.cloudinit.dev/coreci/orca/internal/jobspec"
|
|
"git.cloudinit.dev/coreci/orca/internal/proxmox"
|
|
"git.cloudinit.dev/coreci/orca/internal/traefik"
|
|
)
|
|
|
|
// TestNftEmitter_PostroutingAndDNATTarget (REQ-173) verifies the nft
|
|
// emitter renders the postrouting masquerade chain and supports
|
|
// DNATTarget substitution.
|
|
func TestNftEmitter_PostroutingAndDNATTarget(t *testing.T) {
|
|
files, err := emitter.NftEmitter{}.RenderNftConfig(emitter.NftClusterConfig{
|
|
DNATTarget: "10.99.0.10",
|
|
EnableSNAT: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("RenderNftConfig: %v", err)
|
|
}
|
|
c := files[0].Content
|
|
if !strings.Contains(c, "chain postrouting") {
|
|
t.Errorf("missing postrouting chain:\n%s", c)
|
|
}
|
|
if !strings.Contains(c, "masquerade") {
|
|
t.Errorf("missing masquerade rule:\n%s", c)
|
|
}
|
|
if !strings.Contains(c, "dnat to 10.99.0.10:8443") {
|
|
t.Errorf("missing custom DNAT target:\n%s", c)
|
|
}
|
|
}
|
|
|
|
// TestNftEmitter_PriorityMinus10 (research Topic 2) verifies the input
|
|
// and forward chains use priority -10 for pve-firewall coexistence.
|
|
func TestNftEmitter_PriorityMinus10(t *testing.T) {
|
|
files, _ := emitter.NftEmitter{}.RenderNftConfig(emitter.NftClusterConfig{})
|
|
c := files[0].Content
|
|
if !strings.Contains(c, "hook input priority -10;") {
|
|
t.Errorf("input chain should use priority -10:\n%s", c)
|
|
}
|
|
if !strings.Contains(c, "hook forward priority -10;") {
|
|
t.Errorf("forward chain should use priority -10:\n%s", c)
|
|
}
|
|
}
|
|
|
|
// TestTraefikEmitter_TLSModel (REQ-172) verifies the dynamic config
|
|
// emits tls: {} and does NOT contain certResolver (dropped in v0.14).
|
|
func TestTraefikEmitter_TLSModel(t *testing.T) {
|
|
spec := &jobspec.WorkloadSpec{
|
|
Name: "test-svc",
|
|
Kind: "Service",
|
|
Ports: []jobspec.PortSpec{{Name: "http"}},
|
|
}
|
|
node := &emitter.Node{
|
|
Hostname: "test-node",
|
|
}
|
|
files, err := emitter.TraefikEmitter{}.Render(spec, node)
|
|
if err != nil {
|
|
t.Fatalf("Render: %v", err)
|
|
}
|
|
c := files[0].Content
|
|
if !strings.Contains(c, "tls: {}") {
|
|
t.Errorf("missing tls: {} (v0.14 model):\n%s", c)
|
|
}
|
|
if strings.Contains(c, "certResolver: orca") {
|
|
t.Errorf("certResolver: orca should be removed (v0.14):\n%s", c)
|
|
}
|
|
}
|
|
|
|
// TestTraefikImageRef verifies the image reference resolution for the
|
|
// orca-traefik podman container.
|
|
func TestTraefikImageRef(t *testing.T) {
|
|
ref := traefik.ImageRef("v0.13.7")
|
|
want := "git.cloudinit.dev/coreci/orca-traefik:v0.13.7"
|
|
if ref != want {
|
|
t.Errorf("ImageRef(v0.13.7) = %q, want %q", ref, want)
|
|
}
|
|
// Dev build falls back to latest.
|
|
ref = traefik.ImageRef("dev")
|
|
if ref != "git.cloudinit.dev/coreci/orca-traefik:latest" {
|
|
t.Errorf("ImageRef(dev) = %q, want latest", ref)
|
|
}
|
|
}
|
|
|
|
// TestProxmox_FloatingIP_LXC_ProvisioningCommands (REQ-176) verifies
|
|
// the ProvisionIngressLXC function sends the correct pct create
|
|
// command with the right net0 parameters.
|
|
func TestProxmox_FloatingIP_LXC_ProvisioningCommands(t *testing.T) {
|
|
var cmds []string
|
|
execFn := func(cmd string) ([]byte, error) {
|
|
cmds = append(cmds, cmd)
|
|
// Simulate: pct status returns "absent" on first call, then OK.
|
|
if strings.Contains(cmd, "pct status 201") {
|
|
return []byte("absent\n"), nil
|
|
}
|
|
if strings.Contains(cmd, "pct create") {
|
|
return []byte(""), nil
|
|
}
|
|
if strings.Contains(cmd, "pct start 201") {
|
|
return []byte(""), nil
|
|
}
|
|
if strings.Contains(cmd, "hostname -I") {
|
|
return []byte("203.0.113.10\n"), nil
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
err := proxmox.ProvisionIngressLXC(nil, execFn, proxmox.FloatingIPOptions{
|
|
FloatingIP: "203.0.113.10",
|
|
Gateway: "203.0.113.1",
|
|
MAC: "02:01:02:03:04:05",
|
|
NetPrefix: 24,
|
|
LXCTemplate: "ubuntu-24.04",
|
|
}, slog.Default())
|
|
if err != nil {
|
|
t.Fatalf("ProvisionIngressLXC: %v", err)
|
|
}
|
|
// Verify pct create has the right net0 params.
|
|
foundCreate := false
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "pct create") {
|
|
foundCreate = true
|
|
if !strings.Contains(c, "hostname ingress") {
|
|
t.Errorf("pct create missing hostname ingress: %s", c)
|
|
}
|
|
if !strings.Contains(c, "hwaddr=02:01:02:03:04:05") {
|
|
t.Errorf("pct create missing hwaddr: %s", c)
|
|
}
|
|
if !strings.Contains(c, "ip=203.0.113.10/24") {
|
|
t.Errorf("pct create missing ip: %s", c)
|
|
}
|
|
if !strings.Contains(c, "gw=203.0.113.1") {
|
|
t.Errorf("pct create missing gw: %s", c)
|
|
}
|
|
if !strings.Contains(c, "nesting=1,keyctl=1,fuse=1") {
|
|
t.Errorf("pct create missing features (research Topic 3): %s", c)
|
|
}
|
|
}
|
|
}
|
|
if !foundCreate {
|
|
t.Errorf("pct create command not sent\ncommands: %v", cmds)
|
|
}
|
|
}
|
|
|
|
// TestProxmox_GenerateRandomMAC (D-261) verifies MAC generation produces
|
|
// a valid locally-administered MAC.
|
|
func TestProxmox_GenerateRandomMAC(t *testing.T) {
|
|
mac, err := proxmox.GenerateRandomMAC()
|
|
if err != nil {
|
|
t.Fatalf("GenerateRandomMAC: %v", err)
|
|
}
|
|
if !strings.HasPrefix(mac, "02:") {
|
|
t.Errorf("MAC should start with 02: (locally administered): %s", mac)
|
|
}
|
|
// Verify it's 6 octets.
|
|
parts := strings.Split(mac, ":")
|
|
if len(parts) != 6 {
|
|
t.Errorf("MAC should have 6 octets: %s", mac)
|
|
}
|
|
}
|