package runtime import ( "context" "errors" "strings" "testing" "time" "git.cloudinit.dev/coreci/orca/internal/jobspec" "git.cloudinit.dev/coreci/orca/internal/sshpush" ) // allocNoImage returns an alloc whose Spec has a Runtime block with a // command but no image. func allocNoImage(runtime string) *Alloc { return &Alloc{ ID: "x", Runtime: runtime, Spec: &jobspec.WorkloadSpec{ Runtime: &jobspec.RuntimeBlock{Command: "/bin/true"}, }, } } // TestPodmanRuntime_HappyPath wires a fake server that responds to // podman pull/run/stop/rm/inspect and verifies the full lifecycle. func TestPodmanRuntime_HappyPath(t *testing.T) { srv := newFakeServer(t) defer srv.close() const cid = "abc123def456" srv.setHandler("podman pull", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("podman run", func(cmd string) ([]byte, int) { return []byte(cid + "\n"), 0 }) srv.setHandler("podman stop", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("podman rm", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("podman inspect", func(cmd string) ([]byte, int) { return []byte("true\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "docker.io/library/alpine:latest", "sleep 30", srv.addr()) ctx, cancel := withTimeout(10 * time.Second) defer cancel() if err := p.Prepare(ctx, a); err != nil { t.Fatalf("Prepare: %v", err) } pid, err := p.Start(ctx, a) if err != nil { t.Fatalf("Start: %v", err) } if pid <= 0 { t.Fatalf("pid = %d, want > 0", pid) } st, err := p.Status(ctx, a) if err != nil { t.Fatalf("Status: %v", err) } if st != StateRunning { t.Errorf("Status = %q, want running", st) } if err := p.Stop(ctx, a); err != nil { t.Fatalf("Stop: %v", err) } } // TestPodmanRuntime_StatusFalse verifies Status returns stopped when // the container reports running=false. func TestPodmanRuntime_StatusFalse(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("podman inspect", func(cmd string) ([]byte, int) { return []byte("false\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) st, err := p.Status(context.Background(), a) if err != nil { t.Fatalf("Status: %v", err) } if st != StateStopped { t.Errorf("Status = %q, want stopped", st) } } // TestPodmanRuntime_StatusBadOutput verifies Status returns failed on // unexpected inspect output. func TestPodmanRuntime_StatusBadOutput(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("podman inspect", func(cmd string) ([]byte, int) { return []byte("garbage\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) if _, err := p.Status(context.Background(), a); err == nil { t.Error("Status with bad output should error") } } // TestPodmanRuntime_PrepareNoImage verifies Prepare errors when the // alloc has no image. func TestPodmanRuntime_PrepareNoImage(t *testing.T) { p := NewPodmanRuntime(nil) a := allocNoImage("podman") if err := p.Prepare(context.Background(), a); err == nil { t.Error("Prepare with no image should error") } } // TestPodmanRuntime_PrepareTransportError verifies Prepare propagates a // transport error (podman pull fails). func TestPodmanRuntime_PrepareTransportError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("podman pull", func(cmd string) ([]byte, int) { return []byte("manifest unknown\n"), 2 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) if err := p.Prepare(context.Background(), a); err == nil { t.Error("Prepare with failed pull should error") } } // TestPodmanRuntime_StartNoImage verifies Start errors with no image. func TestPodmanRuntime_StartNoImage(t *testing.T) { p := NewPodmanRuntime(nil) a := allocNoImage("podman") if _, err := p.Start(context.Background(), a); err == nil { t.Error("Start with no image should error") } } // TestPodmanRuntime_StopTransportError verifies Stop propagates errors. func TestPodmanRuntime_StopTransportError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("podman stop", func(cmd string) ([]byte, int) { return []byte("no such container\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) if err := p.Stop(context.Background(), a); err == nil { t.Error("Stop with missing container should error") } } // TestPodmanRuntime_StatusInspectError verifies Status returns failed // when inspect itself errors. func TestPodmanRuntime_StatusInspectError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("podman inspect", func(cmd string) ([]byte, int) { return []byte("no such container\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) st, err := p.Status(context.Background(), a) if err == nil { t.Error("Status with inspect error should error") } if st != StateFailed { t.Errorf("Status = %q, want failed", st) } } // TestPodmanCidToPID verifies the synthetic PID derivation. func TestPodmanCidToPID(t *testing.T) { if got := podmanCidToPID(""); got != 1 { t.Errorf("empty cid -> %d, want 1", got) } if got := podmanCidToPID("a"); got <= 0 { t.Errorf("single hex -> %d, want > 0", got) } if got := podmanCidToPID("abcd"); got <= 0 { t.Errorf("abcd -> %d, want > 0", got) } // non-hex chars fall through to 0 contributions but still yield // a positive result (>= 1 by the floor). if got := podmanCidToPID("xyz123"); got <= 0 { t.Errorf("xyz123 -> %d, want > 0", got) } } // TestContainerNameSanitization verifies the container-name sanitizer // uppercases and strips disallowed characters. func TestContainerNameSanitization(t *testing.T) { a := &Alloc{ID: "ALLOC_1.2.3", Spec: nil, Runtime: "podman"} got := containerName(a) if !strings.HasPrefix(got, "orca-") { t.Errorf("containerName = %q, want orca- prefix", got) } if strings.Contains(got, ".") { t.Errorf("containerName = %q, should not contain '.'", got) } } // TestPodmanRuntime_DialError verifies Prepare fails fast when the // peer is unreachable (no fake server). func TestPodmanRuntime_DialError(t *testing.T) { srv := newFakeServer(t) // close immediately so dial fails. srv.close() tr := realTransport(t, srv) defer tr.Close() p := NewPodmanRuntime(tr) a := allocWithNode("podman", "img", "sleep 1", srv.addr()) if err := p.Prepare(context.Background(), a); err == nil { t.Error("Prepare against dead peer should error") } else if !errors.Is(err, sshpush.ErrTransient) && !errors.Is(err, sshpush.ErrPermanent) { // acceptable: either transient (retry exhausted) or permanent. t.Logf("Prepare err (acceptable): %v", err) } }