9991e3d561
P10 — step-ca cluster CA (D-101) + lead eligibility (R-003). step-ca (internal/stepca/stepca.go, REQ-076): - Client wraps step CLI via SSH on the lead (no Go step-ca client lib). - Init: step ca init --name --dns --address --provisioner orca-admin. Root mirrored to paths.CACertPath() (cluster/ca.crt, v0.9 location). - IssueServerCert: 90-day (2160h) server cert with SANs. IssueSVID: 24h SVID with SPIFFE ID as URI SAN, provisioner orca-admin. RenewServerCert. Fingerprint. 96.6% coverage. Lead rules (internal/cluster/lead.go, R-003): - IsLeadEligible: linux=true, proxmox=false, unknown=false. - ValidateLeadRotation: refuses proxmox nodes with R-003 message, refuses unregistered nodes. 100% coverage. 26 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent. ---ci--- project: orca phase: P10 milestone: v0.9 status: execute ---/ci---
120 lines
3.5 KiB
Go
120 lines
3.5 KiB
Go
package cluster
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsLeadEligible(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
kind NodeKind
|
|
want bool
|
|
}{
|
|
{"linux", NodeKindLinux, true},
|
|
{"proxmox", NodeKindProxmox, false},
|
|
{"empty", "", false},
|
|
{"unknown", NodeKind("foo"), false},
|
|
{"localhost", NodeKind("localhost"), false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := IsLeadEligible(tc.kind); got != tc.want {
|
|
t.Errorf("IsLeadEligible(%q) = %v, want %v", tc.kind, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_LinuxOK(t *testing.T) {
|
|
nodes := []NodeInfo{
|
|
{Hostname: "n1", Kind: NodeKindLinux},
|
|
{Hostname: "n2", Kind: NodeKindLinux},
|
|
{Hostname: "pve1", Kind: NodeKindProxmox},
|
|
}
|
|
if err := ValidateLeadRotation("n2", nodes); err != nil {
|
|
t.Errorf("ValidateLeadRotation(n2): err = %v, want nil", err)
|
|
}
|
|
if err := ValidateLeadRotation("n1", nodes); err != nil {
|
|
t.Errorf("ValidateLeadRotation(n1): err = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_ProxmoxRefused(t *testing.T) {
|
|
nodes := []NodeInfo{
|
|
{Hostname: "n1", Kind: NodeKindLinux},
|
|
{Hostname: "pve1", Kind: NodeKindProxmox},
|
|
}
|
|
err := ValidateLeadRotation("pve1", nodes)
|
|
if err == nil {
|
|
t.Fatal("ValidateLeadRotation(pve1): expected error, got nil")
|
|
}
|
|
if !errors.Is(err, ErrProxmoxNotLead) {
|
|
t.Errorf("err = %v, want ErrProxmoxNotLead", err)
|
|
}
|
|
if got := err.Error(); got != "Proxmox nodes cannot hold the cluster lead role (R-003)" {
|
|
t.Errorf("err message = %q, want R-003 text verbatim", got)
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_UnknownNode(t *testing.T) {
|
|
nodes := []NodeInfo{
|
|
{Hostname: "n1", Kind: NodeKindLinux},
|
|
}
|
|
err := ValidateLeadRotation("ghost", nodes)
|
|
if err == nil {
|
|
t.Fatal("ValidateLeadRotation(ghost): expected error, got nil")
|
|
}
|
|
if !errors.Is(err, ErrNodeNotRegistered) {
|
|
t.Errorf("err = %v, want ErrNodeNotRegistered", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_EmptyList(t *testing.T) {
|
|
err := ValidateLeadRotation("anyone", nil)
|
|
if err == nil {
|
|
t.Fatal("ValidateLeadRotation on empty list: expected error, got nil")
|
|
}
|
|
if !errors.Is(err, ErrNodeNotRegistered) {
|
|
t.Errorf("err = %v, want ErrNodeNotRegistered", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_IneligibleKindRegistered(t *testing.T) {
|
|
// A node registered with a kind that is neither linux nor
|
|
// proxmox (e.g. the auto-registered "localhost" kind) is
|
|
// rejected as ineligible, not as unregistered.
|
|
nodes := []NodeInfo{
|
|
{Hostname: "self", Kind: NodeKind("localhost")},
|
|
}
|
|
err := ValidateLeadRotation("self", nodes)
|
|
if err == nil {
|
|
t.Fatal("expected error for localhost kind, got nil")
|
|
}
|
|
if !errors.Is(err, ErrProxmoxNotLead) {
|
|
t.Errorf("err = %v, want wrapped ErrProxmoxNotLead (ineligible)", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateLeadRotation_CaseSensitive(t *testing.T) {
|
|
// Hostnames are case-normalized at the store layer; the rule
|
|
// matches exactly. "N1" is NOT the same as "n1".
|
|
nodes := []NodeInfo{
|
|
{Hostname: "n1", Kind: NodeKindLinux},
|
|
}
|
|
if err := ValidateLeadRotation("N1", nodes); !errors.Is(err, ErrNodeNotRegistered) {
|
|
t.Errorf("N1 (case mismatch): err = %v, want ErrNodeNotRegistered", err)
|
|
}
|
|
}
|
|
|
|
func TestNodeKindString(t *testing.T) {
|
|
if got := NodeKindLinux.String(); got != "linux" {
|
|
t.Errorf("Linux.String() = %q", got)
|
|
}
|
|
if got := NodeKindProxmox.String(); got != "proxmox" {
|
|
t.Errorf("Proxmox.String() = %q", got)
|
|
}
|
|
// Uppercase constructor should lower-case.
|
|
if got := NodeKind("PROXMOX").String(); got != "proxmox" {
|
|
t.Errorf("PROXMOX.String() = %q, want proxmox", got)
|
|
}
|
|
}
|