Files
ITWorx-Pulse-Public/internal/storagescenarios/scenario_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

108 lines
4.9 KiB
Go

package storagescenarios
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/itworx/pulse/internal/array"
"github.com/itworx/pulse/internal/datasource"
"github.com/itworx/pulse/internal/disk"
"github.com/itworx/pulse/internal/pool"
"github.com/itworx/pulse/internal/reconciliation"
)
type scenario struct {
ID string `json:"id"`
Timeline []json.RawMessage `json:"timeline"`
ExpectedOutcomes []json.RawMessage `json:"expectedOutcomes"`
}
func fixture(t *testing.T, name string) scenario {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("could not locate scenario test")
}
content, err := os.ReadFile(filepath.Join(filepath.Dir(file), "..", "..", "fixtures", "scenarios", name))
if err != nil {
t.Fatal(err)
}
var got scenario
if err := json.Unmarshal(content, &got); err != nil {
t.Fatal(err)
}
if len(got.Timeline) == 0 || len(got.ExpectedOutcomes) == 0 {
t.Fatalf("fixture %s has no executable expectations", name)
}
return got
}
func TestStorageFailureScenarioSuite(t *testing.T) {
for _, name := range []string{"array-degraded.json", "smart-warning.json", "disk-temperature.json", "pool-capacity-pressure.json", "prometheus-stale.json"} {
scenario := fixture(t, name)
if scenario.ID == "" {
t.Fatalf("fixture %s has no stable id", name)
}
}
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
arraySnapshot, err := array.Normalize(array.RawSnapshot{
Source: array.Source{ID: "fixture-array", Type: "fixture"}, State: array.StateDegraded,
Parity: array.RawParity{Present: true, State: "failed"},
Members: []array.RawMember{{ID: "disk-1", Name: "Disk 1", Role: "data", State: "emulated", CapacityBytes: 100}},
ObservedAt: now, ReceivedAt: now,
}, now, array.Limits{}, array.Policy{})
if err != nil || arraySnapshot.State != array.StateDegraded || arraySnapshot.Parity.State != "failed" {
t.Fatalf("array scenario=%+v err=%v", arraySnapshot, err)
}
diskSnapshot, err := disk.Normalize(disk.RawSnapshot{
Source: disk.Source{ID: "fixture-disks", Type: "fixture"},
Disks: []disk.RawDisk{{ID: "disk-smart", Name: "Disk SMART", SizeBytes: 100, SMART: &disk.RawSMART{Available: true, Overall: disk.SMARTPassed, ObservedAt: now, Attributes: []disk.RawSMARTAttribute{{ID: "197", Name: "Current Pending Sector", RawValue: 2}}}, Temperature: &disk.RawTemperature{Available: true, Celsius: 52, ObservedAt: now}}},
ObservedAt: now, ReceivedAt: now,
}, now, disk.Limits{}, disk.Policy{})
if err != nil || diskSnapshot.Disks[0].SMART.Overall != disk.SMARTAttention || diskSnapshot.Disks[0].Temperature.Status != "attention" {
t.Fatalf("disk scenario=%+v err=%v", diskSnapshot, err)
}
poolSnapshot, err := pool.Normalize(pool.RawSnapshot{
Source: pool.Source{ID: "fixture-pools", Type: "fixture"},
Pools: []pool.RawPool{{ID: "cache", Name: "Cache", Filesystem: "btrfs", State: pool.StateHealthy, UsableBytes: 1000, UsedBytes: 950, Capabilities: pool.Capabilities{Capacity: pool.CapabilityAvailable}}},
ObservedAt: now, ReceivedAt: now,
}, now, pool.Limits{}, pool.Policy{})
if err != nil || poolSnapshot.Pools[0].FreeBytes != 50 || poolSnapshot.Pools[0].UtilizationPercent != 95 {
t.Fatalf("pool scenario=%+v err=%v", poolSnapshot, err)
}
previous := []reconciliation.Existing{{EntityID: reconciliation.StableEntityID("source", "container", "abc"), SourceID: "source", ExternalType: "container", ExternalID: "abc"}}
result, err := reconciliation.Reconcile("source", datasource.HealthUnknown, nil, previous, nil, now)
if err != nil || len(result.Entities) != 1 || len(result.Tombstoned) != 0 {
t.Fatalf("source outage removed inventory: %+v err=%v", result, err)
}
}
func TestStorageFalseGreenAndCancellationScenarios(t *testing.T) {
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
stale, err := disk.Normalize(disk.RawSnapshot{Source: disk.Source{ID: "fixture-disks", Type: "fixture"}, Disks: []disk.RawDisk{{ID: "disk", Name: "Disk", SizeBytes: 100, UsedBytes: 40, SMART: &disk.RawSMART{Available: true, Overall: disk.SMARTPassed, ObservedAt: now.Add(-2 * time.Hour)}}}, ObservedAt: now.Add(-2 * time.Hour), ReceivedAt: now.Add(-2 * time.Hour)}, now, disk.Limits{}, disk.Policy{FreshnessMaxAge: time.Minute, SMARTFreshnessMaxAge: time.Hour})
if err != nil || stale.Source.State != disk.StateUnknown || stale.Disks[0].SMART.State != disk.SMARTUnknown {
t.Fatalf("stale data became green: %+v err=%v", stale, err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, err := (array.Adapter{}).Snapshot(ctx); !errors.Is(err, context.Canceled) {
t.Fatalf("array cancellation err=%v", err)
}
if _, err := (disk.Adapter{}).Snapshot(ctx); !errors.Is(err, context.Canceled) {
t.Fatalf("disk cancellation err=%v", err)
}
if _, err := (pool.Adapter{}).Snapshot(ctx); !errors.Is(err, context.Canceled) {
t.Fatalf("pool cancellation err=%v", err)
}
}