test(metis): cover scratch annotation edge cases
This commit is contained in:
parent
c972f226da
commit
8b23c985ca
@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,6 +14,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestScratchHealthAnnotations(t *testing.T) {
|
func TestScratchHealthAnnotations(t *testing.T) {
|
||||||
|
if err := newTestApp(t).syncScratchAnnotations(SnapshotRecord{}); err != nil {
|
||||||
|
t.Fatalf("nil scratch sync should be a noop: %v", err)
|
||||||
|
}
|
||||||
|
if status, detail := scratchStatusDetail(nil); status != "missing" || detail != "no-scratch-snapshot" {
|
||||||
|
t.Fatalf("nil scratch detail mismatch: %s %s", status, detail)
|
||||||
|
}
|
||||||
|
|
||||||
observed := time.Date(2026, 4, 22, 6, 45, 0, 0, time.UTC)
|
observed := time.Date(2026, 4, 22, 6, 45, 0, 0, time.UTC)
|
||||||
annotations := scratchHealthAnnotations(&facts.USBScratch{
|
annotations := scratchHealthAnnotations(&facts.USBScratch{
|
||||||
Mountpoint: "/mnt/astraios",
|
Mountpoint: "/mnt/astraios",
|
||||||
@ -33,8 +41,19 @@ func TestScratchHealthAnnotations(t *testing.T) {
|
|||||||
t.Fatalf("managed paths annotation mismatch: %#v", annotations)
|
t.Fatalf("managed paths annotation mismatch: %#v", annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
status, detail := scratchStatusDetail(&facts.USBScratch{MountHealthy: false, BindHealthy: false})
|
labelAnnotations := scratchHealthAnnotations(&facts.USBScratch{
|
||||||
if status != "error" || !strings.Contains(detail, "mount-unhealthy") || !strings.Contains(detail, "bind-mount-incomplete") {
|
Mountpoint: "/mnt/scratch",
|
||||||
|
Label: "scratch-a",
|
||||||
|
MountHealthy: true,
|
||||||
|
LabelHealthy: true,
|
||||||
|
BindHealthy: true,
|
||||||
|
}, observed)
|
||||||
|
if labelAnnotations["maintenance.bstein.dev/usb-scratch-selector"] != "LABEL=scratch-a" {
|
||||||
|
t.Fatalf("label selector annotation mismatch: %#v", labelAnnotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, detail := scratchStatusDetail(&facts.USBScratch{Label: "scratch-a", MountHealthy: false, LabelHealthy: false, BindHealthy: false})
|
||||||
|
if status != "error" || !strings.Contains(detail, "mount-unhealthy") || !strings.Contains(detail, "label-mismatch") || !strings.Contains(detail, "bind-mount-incomplete") {
|
||||||
t.Fatalf("unexpected unhealthy detail: %s %s", status, detail)
|
t.Fatalf("unexpected unhealthy detail: %s %s", status, detail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,3 +109,77 @@ func TestStoreSnapshotPatchesNodeAnnotations(t *testing.T) {
|
|||||||
t.Fatalf("annotation patch mismatch: %#v", annotations)
|
t.Fatalf("annotation patch mismatch: %#v", annotations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoreSnapshotRecordsAnnotationPatchFailure(t *testing.T) {
|
||||||
|
kube := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
http.Error(w, "denied", http.StatusForbidden)
|
||||||
|
}))
|
||||||
|
defer kube.Close()
|
||||||
|
|
||||||
|
origFactory := kubeClientFactory
|
||||||
|
kubeClientFactory = func() (*kubeClient, error) {
|
||||||
|
return kubeClientFactoryForURL(kube.URL, kube.Client()), nil
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { kubeClientFactory = origFactory })
|
||||||
|
|
||||||
|
app := newTestApp(t)
|
||||||
|
if err := app.StoreSnapshot(SnapshotRecord{
|
||||||
|
Node: "titan-04",
|
||||||
|
CollectedAt: time.Date(2026, 4, 22, 6, 55, 0, 0, time.UTC),
|
||||||
|
Snapshot: sentinel.Snapshot{
|
||||||
|
Hostname: "titan-04",
|
||||||
|
USBScratch: &facts.USBScratch{
|
||||||
|
Mountpoint: "/mnt/astraios",
|
||||||
|
MountHealthy: true,
|
||||||
|
BindHealthy: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("StoreSnapshot should keep the snapshot even if annotation sync fails: %v", err)
|
||||||
|
}
|
||||||
|
state := app.State("")
|
||||||
|
found := false
|
||||||
|
for _, event := range state.Events {
|
||||||
|
if event.Kind == "sentinel.annotation" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected annotation warning event, got %#v", state.Events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPatchNodeAnnotationsNoopsAndClientErrors(t *testing.T) {
|
||||||
|
if err := patchNodeAnnotations("", map[string]string{"a": "b"}); err != nil {
|
||||||
|
t.Fatalf("empty node should be a noop: %v", err)
|
||||||
|
}
|
||||||
|
if err := patchNodeAnnotations("node", nil); err != nil {
|
||||||
|
t.Fatalf("empty annotations should be a noop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
origFactory := kubeClientFactory
|
||||||
|
kubeClientFactory = func() (*kubeClient, error) { return nil, errors.New("offline") }
|
||||||
|
if err := patchNodeAnnotations("node", map[string]string{"a": "b"}); err == nil || !strings.Contains(err.Error(), "offline") {
|
||||||
|
t.Fatalf("expected factory error, got %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { kubeClientFactory = origFactory })
|
||||||
|
|
||||||
|
client := kubeClientFactoryForURL("http://127.0.0.1", &http.Client{Transport: failingRoundTripper{}})
|
||||||
|
if err := client.mergePatch("/api/v1/nodes/node", map[string]string{"a": "b"}); err == nil || !strings.Contains(err.Error(), "transport down") {
|
||||||
|
t.Fatalf("expected transport error, got %v", err)
|
||||||
|
}
|
||||||
|
if err := client.mergePatch("/api/v1/nodes/node", map[string]any{"bad": func() {}}); err == nil {
|
||||||
|
t.Fatal("expected marshal error")
|
||||||
|
}
|
||||||
|
badURLClient := kubeClientFactoryForURL("http://[::1", http.DefaultClient)
|
||||||
|
if err := badURLClient.mergePatch("/api/v1/nodes/node", map[string]string{"a": "b"}); err == nil {
|
||||||
|
t.Fatal("expected request construction error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingRoundTripper struct{}
|
||||||
|
|
||||||
|
func (failingRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
|
||||||
|
return nil, errors.New("transport down")
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user