mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Make fixture graph the only mock runtime API
This commit is contained in:
parent
cbfc7ce750
commit
9187ee727f
24 changed files with 223 additions and 111 deletions
|
|
@ -271,6 +271,11 @@ for API-backed platform onboarding. TrueNAS and VMware connection-list payloads
|
|||
shown in Platform connections must be assembled from the canonical
|
||||
`internal/mock/` platform fixture layer, so settings handoff metadata cannot
|
||||
drift from the runtime mock inventory and shared storage/recovery context.
|
||||
That same lifecycle-adjacent mock path must stay graph-first at the shared
|
||||
`internal/api/` boundary. When lifecycle-adjacent handlers depend on mock
|
||||
platform inventory or recovery context, they must consume
|
||||
`internal/mock/fixture_graph.go` and its graph-owned projections instead of
|
||||
reintroducing snapshot-only or platform-only helper exports.
|
||||
That same hosted continuity contract also applies to the older direct tenant
|
||||
magic-link path. Lifecycle-adjacent control-plane redirects through
|
||||
`/auth/cloud-handoff` must preserve canonical account/user/role identity in the
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ Own canonical runtime payload shapes between backend and frontend.
|
|||
2. Update frontend API types in the same slice
|
||||
3. Route runtime changes through the explicit API-contract proof policies in `registry.json`; default fallback proof routing is not allowed
|
||||
4. Update this contract when canonical payload ownership changes
|
||||
5. Keep mock recovery handlers graph-first: `/api/recovery/rollups`, `/api/recovery/points`, `/api/recovery/series`, and `/api/recovery/facets` must derive mock data from `internal/mock/fixture_graph.go` and its graph-owned `RecoveryPoints()` projection instead of route-local mock caches or legacy helper exports
|
||||
5. Keep `/api/resources` policy metadata aligned across backend payload tests and canonical frontend resource consumers whenever sensitivity or routing fields change
|
||||
6. Keep Patrol status payloads explicit enough that the frontend can present blocked runtime state without treating a previously healthy summary snapshot as current runtime truth, and keep Patrol recency semantics explicit in transport by reserving `last_patrol_at` for completed full patrols while exposing any Patrol activity separately through `last_activity_at`
|
||||
and the scoped-trigger status payload on that same Patrol status surface, so queued scoped work, busy-mode state, and per-source enablement (`alert` versus `anomaly`) stay transport-backed instead of being inferred by page-local heuristics
|
||||
|
|
|
|||
|
|
@ -376,9 +376,10 @@ That same fixture authority now also includes legacy snapshot-backed platforms.
|
|||
`internal/mock/fixture_graph.go` runtime graph as the one mock owner for
|
||||
legacy Proxmox/Docker/Kubernetes/agent/PBS/PMG snapshot state plus
|
||||
provider-backed TrueNAS and VMware fixtures. Monitoring must not rebuild mock
|
||||
provider context from standalone defaults or mix a legacy `GenerateMockData`
|
||||
snapshot with separate provider fixtures when seeding read-state or metrics
|
||||
history.
|
||||
provider context from standalone defaults, consume partial legacy helper
|
||||
exports, or mix snapshot state with separate provider fixtures when seeding
|
||||
read-state or metrics history. The graph and its methods are the canonical
|
||||
mock runtime API.
|
||||
That same boundary now also owns native disk-history fallback when Pulse's own
|
||||
history is shallow. `internal/truenas/client.go`,
|
||||
`internal/truenas/provider.go`, `internal/monitoring/truenas_poller.go`, and
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ querying, and the operator-facing storage health presentation layer.
|
|||
come from the canonical `internal/mock/fixture_graph.go` owner so legacy
|
||||
snapshot-backed platforms, provider-backed fixtures, unified inventory,
|
||||
and recovery/storage context stay aligned instead of drifting through
|
||||
recovery-local fixture assembly.
|
||||
recovery-local fixture assembly or partial mock helper APIs.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
|
|||
|
|
@ -270,6 +270,9 @@ one graph instead of combining a legacy snapshot read with standalone provider
|
|||
defaults. The shared resource graph must therefore see one coherent mock
|
||||
platform set regardless of whether a platform is snapshot-backed or
|
||||
supplemental-provider-backed.
|
||||
Callers should therefore consume `CurrentFixtureGraph()` and graph-owned
|
||||
projections rather than reintroducing platform-only or state-only mock helper
|
||||
exports.
|
||||
TrueNAS-managed applications now follow the same canonical workload rule. One
|
||||
TrueNAS app instance from `app.query` must project as one canonical
|
||||
`app-container` resource under `SourceTrueNAS`, reusing the shared workload and
|
||||
|
|
|
|||
|
|
@ -3146,6 +3146,56 @@ func TestContract_RecoveryPointPayloadUsesCanonicalPlatformField(t *testing.T) {
|
|||
assertJSONSnapshot(t, got, want)
|
||||
}
|
||||
|
||||
func TestContract_RecoveryPointsMockPathReturnsCanonicalProviderBackedFixtures(t *testing.T) {
|
||||
previousEnabled := mock.IsMockEnabled()
|
||||
previousConfig := mock.GetConfig()
|
||||
t.Cleanup(func() {
|
||||
mock.SetEnabled(false)
|
||||
mock.SetMockConfig(previousConfig)
|
||||
if previousEnabled {
|
||||
mock.SetEnabled(true)
|
||||
mock.SetMockConfig(previousConfig)
|
||||
}
|
||||
})
|
||||
|
||||
t.Setenv("PULSE_MOCK_NODES", "1")
|
||||
t.Setenv("PULSE_MOCK_VMS_PER_NODE", "0")
|
||||
t.Setenv("PULSE_MOCK_LXCS_PER_NODE", "0")
|
||||
t.Setenv("PULSE_MOCK_DOCKER_HOSTS", "0")
|
||||
t.Setenv("PULSE_MOCK_DOCKER_CONTAINERS", "0")
|
||||
t.Setenv("PULSE_MOCK_GENERIC_HOSTS", "0")
|
||||
t.Setenv("PULSE_MOCK_K8S_CLUSTERS", "0")
|
||||
t.Setenv("PULSE_MOCK_K8S_NODES", "0")
|
||||
t.Setenv("PULSE_MOCK_K8S_PODS", "0")
|
||||
t.Setenv("PULSE_MOCK_K8S_DEPLOYMENTS", "0")
|
||||
|
||||
mock.SetEnabled(false)
|
||||
mock.SetEnabled(true)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/recovery/points?platform=truenas&limit=10", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
NewRecoveryHandlers(nil).HandleListPoints(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var resp recoveryPointsResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("unmarshal recovery points response: %v", err)
|
||||
}
|
||||
if len(resp.Data) == 0 {
|
||||
t.Fatal("expected provider-backed mock recovery points in response")
|
||||
}
|
||||
if resp.Data[0].Platform != recovery.Provider("truenas") {
|
||||
t.Fatalf("platform = %q, want %q", resp.Data[0].Platform, "truenas")
|
||||
}
|
||||
if resp.Data[0].Display == nil {
|
||||
t.Fatal("expected normalized recovery display payload on mock points response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract_RecoveryRollupPayloadUsesCanonicalPlatformsField(t *testing.T) {
|
||||
payload := buildRecoveryRollupPayload(recovery.ProtectionRollup{
|
||||
RollupID: "rollup-1",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func TestMetricsHistoryFallbackMockDiskSynthesizesSeries(t *testing.T) {
|
|||
mock.SetEnabled(true)
|
||||
t.Cleanup(func() { mock.SetEnabled(false) })
|
||||
|
||||
state := mock.GetMockState()
|
||||
state := mock.CurrentFixtureGraph().State
|
||||
var disk models.PhysicalDisk
|
||||
found := false
|
||||
for _, candidate := range state.PhysicalDisks {
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ func (h *RecoveryHandlers) HandleListPoints(w http.ResponseWriter, r *http.Reque
|
|||
)
|
||||
|
||||
if mock.IsMockEnabled() {
|
||||
all := mock.GetMockRecoveryPoints()
|
||||
all := mock.CurrentFixtureGraph().RecoveryPoints()
|
||||
filtered := filterRecoveryPoints(all, opts)
|
||||
total = len(filtered)
|
||||
points = paginateRecoveryPoints(filtered, opts.Page, opts.Limit)
|
||||
|
|
@ -336,7 +336,7 @@ func (h *RecoveryHandlers) HandleListSeries(w http.ResponseWriter, r *http.Reque
|
|||
|
||||
var series []recovery.PointsSeriesBucket
|
||||
if mock.IsMockEnabled() {
|
||||
all := mock.GetMockRecoveryPoints()
|
||||
all := mock.CurrentFixtureGraph().RecoveryPoints()
|
||||
filtered := filterRecoveryPoints(all, opts)
|
||||
// Count only completed points.
|
||||
series = buildSeriesFromPoints(filtered, opts, tzOffsetMin)
|
||||
|
|
@ -402,7 +402,7 @@ func (h *RecoveryHandlers) HandleListFacets(w http.ResponseWriter, r *http.Reque
|
|||
|
||||
var facets recovery.PointsFacets
|
||||
if mock.IsMockEnabled() {
|
||||
all := mock.GetMockRecoveryPoints()
|
||||
all := mock.CurrentFixtureGraph().RecoveryPoints()
|
||||
filtered := filterRecoveryPoints(all, opts)
|
||||
facets = buildFacetsFromPoints(filtered)
|
||||
} else {
|
||||
|
|
@ -670,7 +670,7 @@ func (h *RecoveryHandlers) HandleListRollups(w http.ResponseWriter, r *http.Requ
|
|||
)
|
||||
|
||||
if mock.IsMockEnabled() {
|
||||
all := mock.GetMockRecoveryPoints()
|
||||
all := mock.CurrentFixtureGraph().RecoveryPoints()
|
||||
filtered := filterRecoveryPointsForRollups(all, opts)
|
||||
rollups = recovery.BuildRollupsFromPoints(filtered)
|
||||
total = len(rollups)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
)
|
||||
|
||||
// GenerateAlertHistory generates historical alert data for testing
|
||||
func GenerateAlertHistory(nodes []models.Node, vms []models.VM, containers []models.Container) []models.Alert {
|
||||
// buildAlertHistory derives historical alert data from the canonical fixture graph.
|
||||
func buildAlertHistory(nodes []models.Node, vms []models.VM, containers []models.Container) []models.Alert {
|
||||
var history []models.Alert
|
||||
|
||||
// Alert types and messages
|
||||
|
|
|
|||
40
internal/mock/canonical_api_guardrails_test.go
Normal file
40
internal/mock/canonical_api_guardrails_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMockPackageDoesNotReintroduceLegacySnapshotExports(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
files := []string{
|
||||
"generator.go",
|
||||
"alert_history.go",
|
||||
"integration.go",
|
||||
"platform_fixtures.go",
|
||||
"recovery_points.go",
|
||||
}
|
||||
forbiddenSnippets := []string{
|
||||
"func GenerateMockData(",
|
||||
"func GenerateAlertHistory(",
|
||||
"func GetMockState(",
|
||||
"func GetMockRecoveryPoints(",
|
||||
"func GetPlatformFixtures(",
|
||||
"func DefaultPlatformFixtures(",
|
||||
}
|
||||
|
||||
for _, path := range files {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read %s: %v", path, err)
|
||||
}
|
||||
source := string(data)
|
||||
for _, snippet := range forbiddenSnippets {
|
||||
if strings.Contains(source, snippet) {
|
||||
t.Fatalf("%s must not reintroduce legacy mock export %q", path, snippet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,13 +65,13 @@ func TestSetMockConfigNormalizesInvalidValues(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataAllowsZeroGuestConfig(t *testing.T) {
|
||||
func TestBuildFixtureStateAllowsZeroGuestConfig(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.NodeCount = 3
|
||||
cfg.VMsPerNode = 0
|
||||
cfg.LXCsPerNode = 0
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
if len(data.Nodes) != cfg.NodeCount {
|
||||
t.Fatalf("expected %d nodes, got %d", cfg.NodeCount, len(data.Nodes))
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ func emptyFixtureGraph() FixtureGraph {
|
|||
}
|
||||
|
||||
func buildFixtureGraph(cfg MockConfig, now time.Time) FixtureGraph {
|
||||
state := GenerateMockData(cfg)
|
||||
state := buildFixtureState(cfg)
|
||||
state.LastUpdate = now
|
||||
|
||||
return FixtureGraph{
|
||||
State: state,
|
||||
AlertHistory: GenerateAlertHistory(state.Nodes, state.VMs, state.Containers),
|
||||
PlatformFixtures: DefaultPlatformFixtures(),
|
||||
AlertHistory: buildAlertHistory(state.Nodes, state.VMs, state.Containers),
|
||||
PlatformFixtures: defaultPlatformFixtures(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ func (g *FixtureGraph) UpdateMetrics(cfg MockConfig, now time.Time) {
|
|||
if g == nil {
|
||||
return
|
||||
}
|
||||
UpdateMetrics(&g.State, cfg)
|
||||
updateFixtureStateMetrics(&g.State, cfg)
|
||||
g.State.LastUpdate = now
|
||||
}
|
||||
|
||||
|
|
@ -89,15 +89,12 @@ func CurrentFixtureGraph() FixtureGraph {
|
|||
return cloneFixtureGraph(mockGraph)
|
||||
}
|
||||
|
||||
func GetPlatformFixtures() PlatformFixtures {
|
||||
func currentOrDefaultPlatformFixtures() PlatformFixtures {
|
||||
if !IsMockEnabled() {
|
||||
return DefaultPlatformFixtures()
|
||||
return defaultPlatformFixtures()
|
||||
}
|
||||
|
||||
dataMu.RLock()
|
||||
defer dataMu.RUnlock()
|
||||
|
||||
return clonePlatformFixtures(mockGraph.PlatformFixtures)
|
||||
return CurrentFixtureGraph().PlatformFixtures
|
||||
}
|
||||
|
||||
func clonePlatformFixtures(in PlatformFixtures) PlatformFixtures {
|
||||
|
|
|
|||
|
|
@ -328,8 +328,9 @@ func generateVirtualDisks() ([]models.Disk, models.Disk) {
|
|||
return disks, aggregated
|
||||
}
|
||||
|
||||
// GenerateMockData creates a simulated state snapshot for demo/test environments.
|
||||
func GenerateMockData(config MockConfig) models.StateSnapshot {
|
||||
// buildFixtureState synthesizes the snapshot-backed portion of the canonical
|
||||
// fixture graph for demo and test environments.
|
||||
func buildFixtureState(config MockConfig) models.StateSnapshot {
|
||||
// rand is automatically seeded in Go 1.20+
|
||||
config = normalizeMockConfig(config)
|
||||
|
||||
|
|
@ -4799,7 +4800,7 @@ func generateSnapshots(vms []models.VM, containers []models.Container) []models.
|
|||
}
|
||||
|
||||
// UpdateMetrics simulates changing metrics over time
|
||||
func UpdateMetrics(data *models.StateSnapshot, config MockConfig) {
|
||||
func updateFixtureStateMetrics(data *models.StateSnapshot, config MockConfig) {
|
||||
updateDockerHosts(data, config)
|
||||
updateKubernetesClusters(data, config)
|
||||
updateHosts(data, config)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import (
|
|||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
)
|
||||
|
||||
func TestGenerateMockDataIncludesDockerHosts(t *testing.T) {
|
||||
func TestBuildFixtureStateIncludesDockerHosts(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.DockerHostCount = 2
|
||||
cfg.DockerContainersPerHost = 5
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
if len(data.DockerHosts) != cfg.DockerHostCount {
|
||||
t.Fatalf("expected %d docker hosts, got %d", cfg.DockerHostCount, len(data.DockerHosts))
|
||||
|
|
@ -43,7 +43,7 @@ func TestComputeGuestCountsHandlesZeroBaselines(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataWithZeroGuestBaselinesDoesNotPanic(t *testing.T) {
|
||||
func TestBuildFixtureStateWithZeroGuestBaselinesDoesNotPanic(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.NodeCount = 4
|
||||
cfg.VMsPerNode = 0
|
||||
|
|
@ -56,21 +56,21 @@ func TestGenerateMockDataWithZeroGuestBaselinesDoesNotPanic(t *testing.T) {
|
|||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("GenerateMockData panicked on iteration %d: %v", i, r)
|
||||
t.Fatalf("buildFixtureState panicked on iteration %d: %v", i, r)
|
||||
}
|
||||
}()
|
||||
_ = GenerateMockData(cfg)
|
||||
_ = buildFixtureState(cfg)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataIncludesSwarmServices(t *testing.T) {
|
||||
func TestBuildFixtureStateIncludesSwarmServices(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.DockerHostCount = 4
|
||||
cfg.DockerContainersPerHost = 6
|
||||
cfg.RandomMetrics = false
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
found := false
|
||||
for _, host := range data.DockerHosts {
|
||||
|
|
@ -92,12 +92,12 @@ func TestGenerateMockDataIncludesSwarmServices(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataIncludesHostAgents(t *testing.T) {
|
||||
func TestBuildFixtureStateIncludesHostAgents(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.GenericHostCount = 5
|
||||
cfg.RandomMetrics = false
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
expectedMin := cfg.GenericHostCount
|
||||
if cfg.NodeCount > expectedMin {
|
||||
|
|
@ -120,13 +120,13 @@ func TestGenerateMockDataIncludesHostAgents(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataLinksAllNodesToHostAgents(t *testing.T) {
|
||||
func TestBuildFixtureStateLinksAllNodesToHostAgents(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.NodeCount = 7
|
||||
cfg.GenericHostCount = 2
|
||||
cfg.RandomMetrics = false
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
if len(data.Hosts) < cfg.NodeCount {
|
||||
t.Fatalf("expected enough hosts to link all nodes, got hosts=%d nodes=%d", len(data.Hosts), cfg.NodeCount)
|
||||
|
|
@ -151,13 +151,13 @@ func TestGenerateMockDataLinksAllNodesToHostAgents(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataPopulatesHostIORates(t *testing.T) {
|
||||
func TestBuildFixtureStatePopulatesHostIORates(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.NodeCount = 6
|
||||
cfg.GenericHostCount = 1
|
||||
cfg.RandomMetrics = true
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
for _, host := range data.Hosts {
|
||||
if host.Status == "offline" {
|
||||
|
|
@ -175,12 +175,12 @@ func TestGenerateMockDataPopulatesHostIORates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataPopulatesDockerHostIORates(t *testing.T) {
|
||||
func TestBuildFixtureStatePopulatesDockerHostIORates(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.DockerHostCount = 3
|
||||
cfg.RandomMetrics = true
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
if len(data.DockerHosts) == 0 {
|
||||
t.Fatal("expected docker hosts in mock data")
|
||||
|
|
@ -208,14 +208,14 @@ func TestGenerateMockDataPopulatesDockerHostIORates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataPopulatesKubernetesUsageMetrics(t *testing.T) {
|
||||
func TestBuildFixtureStatePopulatesKubernetesUsageMetrics(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.K8sClusterCount = 1
|
||||
cfg.K8sNodesPerCluster = 4
|
||||
cfg.K8sPodsPerCluster = 24
|
||||
cfg.RandomMetrics = false
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
if len(data.KubernetesClusters) != 1 {
|
||||
t.Fatalf("expected exactly one kubernetes cluster, got %d", len(data.KubernetesClusters))
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ func TestGenerateMockDataPopulatesKubernetesUsageMetrics(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataCreatesHostEntriesForKubernetesNodes(t *testing.T) {
|
||||
func TestBuildFixtureStateCreatesHostEntriesForKubernetesNodes(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
cfg.K8sClusterCount = 1
|
||||
cfg.K8sNodesPerCluster = 3
|
||||
|
|
@ -277,7 +277,7 @@ func TestGenerateMockDataCreatesHostEntriesForKubernetesNodes(t *testing.T) {
|
|||
cfg.NodeCount = 0
|
||||
cfg.RandomMetrics = false
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
if len(data.KubernetesClusters) == 0 {
|
||||
t.Fatal("expected kubernetes clusters in mock data")
|
||||
}
|
||||
|
|
@ -310,7 +310,7 @@ func TestMockStateIncludesHostAgents(t *testing.T) {
|
|||
SetEnabled(false)
|
||||
})
|
||||
|
||||
state := GetMockState()
|
||||
state := CurrentFixtureGraph().State
|
||||
if len(state.Hosts) == 0 {
|
||||
t.Fatalf("expected hosts in mock state, got %d", len(state.Hosts))
|
||||
}
|
||||
|
|
@ -329,8 +329,8 @@ func TestUpdateMetricsMaintainsServiceHealth(t *testing.T) {
|
|||
cfg.DockerHostCount = 3
|
||||
cfg.DockerContainersPerHost = 6
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
UpdateMetrics(&data, cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
updateFixtureStateMetrics(&data, cfg)
|
||||
|
||||
for _, host := range data.DockerHosts {
|
||||
if len(host.Services) == 0 {
|
||||
|
|
@ -354,10 +354,10 @@ func TestUpdateMetricsMaintainsServiceHealth(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataIncludesPMGInstances(t *testing.T) {
|
||||
func TestBuildFixtureStateIncludesPMGInstances(t *testing.T) {
|
||||
cfg := DefaultConfig
|
||||
|
||||
data := GenerateMockData(cfg)
|
||||
data := buildFixtureState(cfg)
|
||||
|
||||
if len(data.PMGInstances) == 0 {
|
||||
t.Fatalf("expected PMG instances in mock data")
|
||||
|
|
@ -432,16 +432,16 @@ func TestCloneStatePreservesIgnoredInfrastructureEntries(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMockDataInitializesIgnoredInfrastructureSlices(t *testing.T) {
|
||||
data := GenerateMockData(DefaultConfig)
|
||||
func TestBuildFixtureStateInitializesIgnoredInfrastructureSlices(t *testing.T) {
|
||||
data := buildFixtureState(DefaultConfig)
|
||||
|
||||
if data.RemovedDockerHosts == nil {
|
||||
t.Fatal("expected GenerateMockData to initialize RemovedDockerHosts")
|
||||
t.Fatal("expected buildFixtureState to initialize RemovedDockerHosts")
|
||||
}
|
||||
if data.RemovedHostAgents == nil {
|
||||
t.Fatal("expected GenerateMockData to initialize RemovedHostAgents")
|
||||
t.Fatal("expected buildFixtureState to initialize RemovedHostAgents")
|
||||
}
|
||||
if data.RemovedKubernetesClusters == nil {
|
||||
t.Fatal("expected GenerateMockData to initialize RemovedKubernetesClusters")
|
||||
t.Fatal("expected buildFixtureState to initialize RemovedKubernetesClusters")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -428,18 +428,6 @@ func SetMockConfig(cfg MockConfig) {
|
|||
Msg("Mock configuration updated")
|
||||
}
|
||||
|
||||
// GetMockState returns the current mock state snapshot.
|
||||
func GetMockState() models.StateSnapshot {
|
||||
if !IsMockEnabled() {
|
||||
return models.EmptyStateSnapshot()
|
||||
}
|
||||
|
||||
dataMu.RLock()
|
||||
defer dataMu.RUnlock()
|
||||
|
||||
return cloneState(mockGraph.State)
|
||||
}
|
||||
|
||||
// UpdateAlertSnapshots replaces the active and recently resolved alert lists used for mock mode.
|
||||
// This lets other components read alert data without querying the live alert manager, which can
|
||||
// be locked while alerts are being generated. Keeping a snapshot here prevents any blocking when
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ type VMwareConnectionFixture struct {
|
|||
VIRelease string
|
||||
}
|
||||
|
||||
func DefaultPlatformFixtures() PlatformFixtures {
|
||||
func defaultPlatformFixtures() PlatformFixtures {
|
||||
return PlatformFixtures{
|
||||
TrueNAS: truenas.DefaultFixtures(),
|
||||
VMware: vmware.DefaultFixtures(),
|
||||
|
|
@ -59,11 +59,11 @@ func DefaultPlatformFixtures() PlatformFixtures {
|
|||
}
|
||||
|
||||
func DefaultTrueNASConnectionFixture() TrueNASConnectionFixture {
|
||||
return defaultTrueNASConnectionFixture(GetPlatformFixtures())
|
||||
return defaultTrueNASConnectionFixture(currentOrDefaultPlatformFixtures())
|
||||
}
|
||||
|
||||
func DefaultVMwareConnectionFixture() VMwareConnectionFixture {
|
||||
fixtures := GetPlatformFixtures().VMware
|
||||
fixtures := currentOrDefaultPlatformFixtures().VMware
|
||||
|
||||
return VMwareConnectionFixture{
|
||||
ID: strings.TrimSpace(fixtures.ConnectionID),
|
||||
|
|
@ -108,7 +108,7 @@ func defaultTrueNASConnectionFixture(fixtures PlatformFixtures) TrueNASConnectio
|
|||
}
|
||||
|
||||
func SupplementalRecords(source unifiedresources.DataSource) []unifiedresources.IngestRecord {
|
||||
fixtures := GetPlatformFixtures()
|
||||
fixtures := currentOrDefaultPlatformFixtures()
|
||||
switch normalizePlatformSource(source) {
|
||||
case unifiedresources.SourceTrueNAS:
|
||||
return truenas.FixtureRecords(fixtures.TrueNAS)
|
||||
|
|
|
|||
|
|
@ -11,14 +11,6 @@ import (
|
|||
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
|
||||
)
|
||||
|
||||
func GetMockRecoveryPoints() []recovery.RecoveryPoint {
|
||||
if !IsMockEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CurrentFixtureGraph().RecoveryPoints()
|
||||
}
|
||||
|
||||
func (g FixtureGraph) RecoveryPoints() []recovery.RecoveryPoint {
|
||||
return generateMockRecoveryPoints(g.State, g.PlatformFixtures)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func TestCurrentFixtureGraphReturnsDefensiveCopies(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetMockRecoveryPointsDerivesSubjectsFromCurrentGraph(t *testing.T) {
|
||||
func TestFixtureGraphRecoveryPointsDeriveSubjectsFromCurrentGraph(t *testing.T) {
|
||||
previous := IsMockEnabled()
|
||||
SetEnabled(true)
|
||||
t.Cleanup(func() { SetEnabled(previous) })
|
||||
|
|
@ -82,7 +82,7 @@ func TestGetMockRecoveryPointsDerivesSubjectsFromCurrentGraph(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
points := GetMockRecoveryPoints()
|
||||
points := CurrentFixtureGraph().RecoveryPoints()
|
||||
if len(points) == 0 {
|
||||
t.Fatal("expected mock recovery points")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,22 @@ func TestNoGetStateResourceArrayRegression(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMonitoringRuntimeAvoidsLegacyMockPartialHelpers(t *testing.T) {
|
||||
forbiddenSnippets := []string{
|
||||
"mock.GetMockState(",
|
||||
"mock.GetPlatformFixtures(",
|
||||
"mock.GetMockRecoveryPoints(",
|
||||
}
|
||||
|
||||
for name, content := range readMonitoringRuntimeFiles(t) {
|
||||
for _, snippet := range forbiddenSnippets {
|
||||
if strings.Contains(content, snippet) {
|
||||
t.Fatalf("%s must not depend on legacy mock partial helper %q", name, snippet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectedInfrastructureUsesSharedTopLevelSystemResolver(t *testing.T) {
|
||||
data, err := os.ReadFile("connected_infrastructure.go")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -479,10 +479,11 @@ func buildTieredTimestamps(now time.Time, totalDuration time.Duration) []time.Ti
|
|||
return timestamps
|
||||
}
|
||||
|
||||
func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.StateSnapshot, now time.Time, seedDuration, interval time.Duration) {
|
||||
func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, graph mock.FixtureGraph, now time.Time, seedDuration, interval time.Duration) {
|
||||
if mh == nil {
|
||||
return
|
||||
}
|
||||
state := graph.State
|
||||
if seedDuration <= 0 || interval <= 0 {
|
||||
return
|
||||
}
|
||||
|
|
@ -875,7 +876,7 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
platformFixtures := mock.GetPlatformFixtures()
|
||||
platformFixtures := graph.PlatformFixtures
|
||||
trueNASFixtures := platformFixtures.TrueNAS
|
||||
log.Debug().Int("pools", len(trueNASFixtures.Pools)).Int("datasets", len(trueNASFixtures.Datasets)).Msg("mock seeding: processing TrueNAS fixtures")
|
||||
|
||||
|
|
@ -1006,24 +1007,24 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
}
|
||||
|
||||
// recordTrueNASFixturesMetrics records disk usage metrics for TrueNAS pools and datasets.
|
||||
func recordTrueNASFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time.Time) {
|
||||
fixtures := mock.GetPlatformFixtures().TrueNAS
|
||||
func recordTrueNASFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, fixtures mock.PlatformFixtures, ts time.Time) {
|
||||
snapshot := fixtures.TrueNAS
|
||||
|
||||
totalCap, totalUsed := int64(0), int64(0)
|
||||
for _, pool := range fixtures.Pools {
|
||||
for _, pool := range snapshot.Pools {
|
||||
totalCap += pool.TotalBytes
|
||||
totalUsed += pool.UsedBytes
|
||||
}
|
||||
if totalCap > 0 {
|
||||
systemKey := "system:" + fixtures.System.Hostname
|
||||
systemKey := "system:" + snapshot.System.Hostname
|
||||
systemDisk := float64(totalUsed) / float64(totalCap) * 100
|
||||
mh.AddGuestMetric(systemKey, "disk", systemDisk, ts)
|
||||
if ms != nil {
|
||||
ms.Write("truenas", fixtures.System.Hostname, "disk", systemDisk, ts)
|
||||
ms.Write("truenas", snapshot.System.Hostname, "disk", systemDisk, ts)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pool := range fixtures.Pools {
|
||||
for _, pool := range snapshot.Pools {
|
||||
if pool.TotalBytes > 0 {
|
||||
poolKey := "pool:" + pool.Name
|
||||
diskPct := float64(pool.UsedBytes) / float64(pool.TotalBytes) * 100
|
||||
|
|
@ -1034,7 +1035,7 @@ func recordTrueNASFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time
|
|||
}
|
||||
}
|
||||
|
||||
for _, dataset := range fixtures.Datasets {
|
||||
for _, dataset := range snapshot.Datasets {
|
||||
totalBytes := dataset.UsedBytes + dataset.AvailBytes
|
||||
if totalBytes > 0 {
|
||||
dsKey := "dataset:" + dataset.Name
|
||||
|
|
@ -1047,8 +1048,8 @@ func recordTrueNASFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time
|
|||
}
|
||||
}
|
||||
|
||||
func recordVMwareFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time.Time) {
|
||||
snapshot := mock.GetPlatformFixtures().VMware
|
||||
func recordVMwareFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, fixtures mock.PlatformFixtures, ts time.Time) {
|
||||
snapshot := fixtures.VMware
|
||||
datastoreUsage := vmwareDatastoreUsageByID(snapshot.Datastores)
|
||||
|
||||
for _, host := range snapshot.Hosts {
|
||||
|
|
@ -1251,10 +1252,11 @@ func adaptContainers(cts []models.Container) []containerAdapter {
|
|||
return result
|
||||
}
|
||||
|
||||
func recordMockStateToMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.StateSnapshot, ts time.Time) {
|
||||
func recordMockStateToMetricsHistory(mh *MetricsHistory, ms *metrics.Store, graph mock.FixtureGraph, ts time.Time) {
|
||||
if mh == nil {
|
||||
return
|
||||
}
|
||||
state := graph.State
|
||||
|
||||
for _, node := range state.Nodes {
|
||||
if node.ID == "" || node.Status != "online" {
|
||||
|
|
@ -1453,8 +1455,8 @@ func recordMockStateToMetricsHistory(mh *MetricsHistory, ms *metrics.Store, stat
|
|||
}
|
||||
|
||||
// Record TrueNAS pool/dataset disk-usage live ticks
|
||||
recordTrueNASFixturesMetrics(mh, ms, ts)
|
||||
recordVMwareFixturesMetrics(mh, ms, ts)
|
||||
recordTrueNASFixturesMetrics(mh, ms, graph.PlatformFixtures, ts)
|
||||
recordVMwareFixturesMetrics(mh, ms, graph.PlatformFixtures, ts)
|
||||
}
|
||||
|
||||
func diskMetricsResourceID(disk models.PhysicalDisk) string {
|
||||
|
|
@ -1515,8 +1517,8 @@ func (m *Monitor) startMockMetricsSampler(ctx context.Context) {
|
|||
Msg("Mock metrics sampler: seeding historical data")
|
||||
// Keep mock trend generation in-memory only so production history in the
|
||||
// persistent metrics store remains untouched while mock mode is active.
|
||||
seedMockMetricsHistory(m.metricsHistory, nil, state, time.Now(), seedDuration, cfg.SampleInterval)
|
||||
recordMockStateToMetricsHistory(m.metricsHistory, nil, state, time.Now())
|
||||
seedMockMetricsHistory(m.metricsHistory, nil, graph, time.Now(), seedDuration, cfg.SampleInterval)
|
||||
recordMockStateToMetricsHistory(m.metricsHistory, nil, graph, time.Now())
|
||||
|
||||
m.mockMetricsWg.Add(1)
|
||||
go func() {
|
||||
|
|
@ -1533,7 +1535,7 @@ func (m *Monitor) startMockMetricsSampler(ctx context.Context) {
|
|||
if !mock.IsMockEnabled() {
|
||||
continue
|
||||
}
|
||||
recordMockStateToMetricsHistory(m.metricsHistory, nil, mock.CurrentFixtureGraph().State, time.Now())
|
||||
recordMockStateToMetricsHistory(m.metricsHistory, nil, mock.CurrentFixtureGraph(), time.Now())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -9,9 +9,15 @@ import (
|
|||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
|
||||
)
|
||||
|
||||
func fixtureGraphWithState(state models.StateSnapshot) mock.FixtureGraph {
|
||||
return mock.FixtureGraph{State: state}
|
||||
}
|
||||
|
||||
func TestSeedMockMetricsHistory_PopulatesSeries(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
|
|
@ -77,7 +83,7 @@ func TestSeedMockMetricsHistory_PopulatesSeries(t *testing.T) {
|
|||
}
|
||||
|
||||
mh := NewMetricsHistory(1000, 24*time.Hour)
|
||||
seedMockMetricsHistory(mh, nil, state, now, time.Hour, 30*time.Second)
|
||||
seedMockMetricsHistory(mh, nil, fixtureGraphWithState(state), now, time.Hour, 30*time.Second)
|
||||
|
||||
nodeCPU := mh.GetNodeMetrics("node-1", "cpu", time.Hour)
|
||||
if len(nodeCPU) < 10 {
|
||||
|
|
@ -129,7 +135,7 @@ func TestSeedMockMetricsHistory_PopulatesKubernetesPodSeries(t *testing.T) {
|
|||
}
|
||||
|
||||
mh := NewMetricsHistory(1000, 24*time.Hour)
|
||||
seedMockMetricsHistory(mh, nil, state, now, time.Hour, 30*time.Second)
|
||||
seedMockMetricsHistory(mh, nil, fixtureGraphWithState(state), now, time.Hour, 30*time.Second)
|
||||
|
||||
metricID := kubernetesPodMetricID(state.KubernetesClusters[0], state.KubernetesClusters[0].Pods[0])
|
||||
if metricID == "" {
|
||||
|
|
@ -203,7 +209,7 @@ func TestSeedMockMetricsHistory_SeedsMetricsStore(t *testing.T) {
|
|||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, state, now, 7*24*time.Hour, time.Minute)
|
||||
seedMockMetricsHistory(mh, store, fixtureGraphWithState(state), now, 7*24*time.Hour, time.Minute)
|
||||
|
||||
points, err := store.Query("vm", "vm-100", "cpu", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
|
|
@ -255,7 +261,7 @@ func TestSeedMockMetricsHistory_SeedsDiskTemperatureMetricsStore(t *testing.T) {
|
|||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, state, now, 7*24*time.Hour, time.Minute)
|
||||
seedMockMetricsHistory(mh, store, fixtureGraphWithState(state), now, 7*24*time.Hour, time.Minute)
|
||||
|
||||
points, err := store.Query("disk", "SERIAL-001", "smart_temp", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
|
|
@ -284,7 +290,12 @@ func TestSeedMockMetricsHistory_SeedsVMwareMetricsStore(t *testing.T) {
|
|||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, state, now, 7*24*time.Hour, time.Minute)
|
||||
seedMockMetricsHistory(mh, store, mock.FixtureGraph{
|
||||
State: state,
|
||||
PlatformFixtures: mock.PlatformFixtures{
|
||||
VMware: vmware.DefaultFixtures(),
|
||||
},
|
||||
}, now, 7*24*time.Hour, time.Minute)
|
||||
|
||||
hostPoints, err := store.Query("agent", "vc-mock-1:host:host-101", "cpu", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
|
|
@ -314,7 +325,7 @@ func TestSeedMockMetricsHistory_SeedsVMwareMetricsStore(t *testing.T) {
|
|||
func TestSeedMockMetricsHistory_SeedsTrueNASMetricsStore(t *testing.T) {
|
||||
now := time.Now()
|
||||
state := models.StateSnapshot{}
|
||||
fixtures := mock.DefaultPlatformFixtures().TrueNAS
|
||||
fixtures := truenas.DefaultFixtures()
|
||||
|
||||
if strings.TrimSpace(fixtures.System.Hostname) == "" {
|
||||
t.Fatal("expected canonical truenas system hostname fixture")
|
||||
|
|
@ -337,7 +348,12 @@ func TestSeedMockMetricsHistory_SeedsTrueNASMetricsStore(t *testing.T) {
|
|||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, state, now, 7*24*time.Hour, time.Minute)
|
||||
seedMockMetricsHistory(mh, store, mock.FixtureGraph{
|
||||
State: state,
|
||||
PlatformFixtures: mock.PlatformFixtures{
|
||||
TrueNAS: fixtures,
|
||||
},
|
||||
}, now, 7*24*time.Hour, time.Minute)
|
||||
|
||||
systemPoints, err := store.Query("truenas", fixtures.System.Hostname, "disk", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
|
|
@ -408,7 +424,7 @@ func TestSeedMockMetricsHistory_UsesCanonicalMockFixtureGraphForLegacyAndProvide
|
|||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, graph.State, now, 7*24*time.Hour, time.Minute)
|
||||
seedMockMetricsHistory(mh, store, graph, now, 7*24*time.Hour, time.Minute)
|
||||
|
||||
nodePoints, err := store.Query("node", graph.State.Nodes[0].ID, "cpu", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1194,7 +1194,7 @@ func (m *Monitor) GetConnectionStatuses() map[string]bool {
|
|||
|
||||
if mock.IsMockEnabled() {
|
||||
statuses := make(map[string]bool)
|
||||
state := mock.GetMockState()
|
||||
state := mock.CurrentFixtureGraph().State
|
||||
for _, node := range state.Nodes {
|
||||
key := "pve-" + node.Name
|
||||
statuses[key] = strings.ToLower(node.Status) == "online"
|
||||
|
|
@ -2667,11 +2667,11 @@ func (m *Monitor) GetState() models.StateSnapshot {
|
|||
|
||||
// Check if mock mode is enabled
|
||||
if mock.IsMockEnabled() {
|
||||
state := mock.GetMockState()
|
||||
state := mock.CurrentFixtureGraph().State
|
||||
if state.ActiveAlerts == nil && m.alertManager != nil {
|
||||
// Populate snapshot lazily if the cache hasn't been filled yet.
|
||||
mock.UpdateAlertSnapshots(m.alertManager.GetActiveAlerts(), m.alertManager.GetRecentlyResolved())
|
||||
state = mock.GetMockState()
|
||||
state = mock.CurrentFixtureGraph().State
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ func (m *Monitor) checkMockAlerts() {
|
|||
}
|
||||
|
||||
// Get mock state
|
||||
state := mock.GetMockState()
|
||||
state := mock.CurrentFixtureGraph().State
|
||||
|
||||
log.Info().
|
||||
Int("vms", len(state.VMs)).
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func (m *Monitor) listRecoveryRollupsForAlerts(ctx context.Context, kind recover
|
|||
}
|
||||
|
||||
if mock.IsMockEnabled() {
|
||||
points := mock.GetMockRecoveryPoints()
|
||||
points := mock.CurrentFixtureGraph().RecoveryPoints()
|
||||
filtered := make([]recovery.RecoveryPoint, 0, len(points))
|
||||
for _, p := range points {
|
||||
if kind != "" && p.Kind != kind {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue