mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Modernize platform mock runtime fixtures
This commit is contained in:
parent
c511638acc
commit
a09f61d214
22 changed files with 651 additions and 118 deletions
|
|
@ -266,6 +266,11 @@ running server between real and mock data, the canonical TrueNAS and VMware
|
|||
settings routes must keep surfacing through the same Platform connections
|
||||
workspace and handoff URLs instead of depending on process-start-only wiring
|
||||
or a mock-only alternate shell.
|
||||
That same lifecycle-owned mock path now also requires one shared fixture owner
|
||||
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 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
|
||||
|
|
|
|||
|
|
@ -712,6 +712,12 @@ inventory through `source=truenas` and `source=vmware-vsphere`. Shared query
|
|||
parsing may accept `vmware-vsphere` as the operator-facing VMware alias, but
|
||||
the emitted canonical resource source remains the shared `vmware` source
|
||||
family rather than a second backend source key.
|
||||
That same runtime mock contract now also owns fixture authority. Mock
|
||||
connection payloads returned from `/api/truenas/connections` and
|
||||
`/api/vmware/connections` must derive from the shared `internal/mock/`
|
||||
platform fixture owner rather than handler-local fixture assembly, so settings
|
||||
payloads stay aligned with the unified runtime mock graph, storage/recovery
|
||||
context, and seeded monitoring history.
|
||||
That same VMware test contract now also owns structured setup-failure
|
||||
classification. When `POST /api/vmware/connections/test` or
|
||||
`POST /api/vmware/connections/{id}/test` fails, the backend payload must
|
||||
|
|
|
|||
|
|
@ -365,6 +365,11 @@ When `/api/system/mock-mode` changes on a live server, the TrueNAS supplemental
|
|||
provider must rebind immediately and repopulate the canonical read state so
|
||||
settings, infrastructure, storage, and other shared consumers see the same
|
||||
mock-backed inventory without restart.
|
||||
That same runtime mock ownership now also includes fixture authority. Mock
|
||||
TrueNAS and VMware inventory plus mock metrics-history seeding must derive from
|
||||
one shared platform fixture owner in `internal/mock/` so settings payloads,
|
||||
supplemental ingest, unified read-state, and seeded charts cannot drift from
|
||||
each other when the v6 runtime runs in mock mode.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -148,6 +148,12 @@ querying, and the operator-facing storage health presentation layer.
|
|||
data remains inventory-only context and must not be treated as proof of
|
||||
restore capability, recovery artifacts, or widened platform recovery
|
||||
support.
|
||||
11. Keep runtime mock platform context derived from one shared fixture owner.
|
||||
When shared `internal/api/` and monitoring wiring surface mock TrueNAS or
|
||||
VMware storage/recovery-adjacent inventory, that data must come from the
|
||||
canonical `internal/mock/` platform fixture layer so settings payloads,
|
||||
unified inventory, and recovery/storage context stay aligned instead of
|
||||
drifting through recovery-local fixture assembly.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
@ -1671,6 +1677,12 @@ runtime mock data. Mock TrueNAS pools/datasets and mock VMware datastores may
|
|||
surface through the shared storage and recovery-adjacent pages as canonical
|
||||
inventory context, but that visibility still does not widen Pulse's recovery
|
||||
support claim or imply restore capability for either platform.
|
||||
That same bounded mock contract now also requires one shared fixture authority.
|
||||
When storage or recovery surfaces render mock TrueNAS pools, datasets,
|
||||
snapshots, replication context, or mock VMware datastores, that inventory must
|
||||
come from the same `internal/mock/` platform fixture owner that drives settings
|
||||
payloads and unified runtime inventory, rather than recovery-local fixture
|
||||
assembly or page-local compatibility fallbacks.
|
||||
Storage and recovery must not infer VMware restore support, recovery rollups,
|
||||
or VMware-local protection semantics from the presence of those datastores or
|
||||
VM snapshot-read context on the shared pages.
|
||||
|
|
|
|||
|
|
@ -348,6 +348,121 @@ func TestContract_PlatformMockToggleRebindsRuntimeConnectionsAndResources(t *tes
|
|||
assertResourceSource("/api/resources?source=vmware-vsphere", unifiedresources.SourceVMware)
|
||||
}
|
||||
|
||||
func TestContract_PlatformMockConnectionListsUseSharedFixtureMetadata(t *testing.T) {
|
||||
setTrueNASFeatureForTest(t, true)
|
||||
setVMwareFeatureForTest(t, true)
|
||||
|
||||
prevMock := mock.IsMockEnabled()
|
||||
mock.SetEnabled(true)
|
||||
t.Cleanup(func() {
|
||||
mock.SetEnabled(prevMock)
|
||||
})
|
||||
|
||||
t.Run("truenas", func(t *testing.T) {
|
||||
fixture := mock.DefaultTrueNASConnectionFixture()
|
||||
if fixture.CollectedAt.IsZero() {
|
||||
t.Fatal("expected canonical truenas mock fixture timestamp")
|
||||
}
|
||||
|
||||
handler, _, _ := newTrueNASHandlersForTest(t, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/truenas/connections", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleList(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var responses []trueNASConnectionResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
|
||||
t.Fatalf("decode truenas mock list response: %v", err)
|
||||
}
|
||||
if len(responses) != 1 {
|
||||
t.Fatalf("expected 1 mock truenas connection, got %d", len(responses))
|
||||
}
|
||||
|
||||
response := responses[0]
|
||||
if response.ID != fixture.ID || response.Name != fixture.Name || response.Host != fixture.Host || response.Port != fixture.Port {
|
||||
t.Fatalf("unexpected truenas mock connection metadata: got %+v want fixture %+v", response.TrueNASInstance, fixture)
|
||||
}
|
||||
if response.APIKey != "********" {
|
||||
t.Fatalf("expected redacted truenas api key, got %q", response.APIKey)
|
||||
}
|
||||
if response.Poll == nil || response.Poll.IntervalSeconds != fixture.PollIntervalSeconds {
|
||||
t.Fatalf("expected truenas poll interval %d, got %+v", fixture.PollIntervalSeconds, response.Poll)
|
||||
}
|
||||
if response.Poll.LastSuccessAt == nil || !response.Poll.LastSuccessAt.Equal(fixture.CollectedAt) {
|
||||
t.Fatalf("expected truenas last success at %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Poll)
|
||||
}
|
||||
if response.Observed == nil {
|
||||
t.Fatal("expected truenas observed summary")
|
||||
}
|
||||
if response.Observed.Host != fixture.Host ||
|
||||
response.Observed.ResourceID != fixture.ResourceID ||
|
||||
response.Observed.Systems != fixture.Systems ||
|
||||
response.Observed.StoragePools != fixture.StoragePools ||
|
||||
response.Observed.Datasets != fixture.Datasets ||
|
||||
response.Observed.Apps != fixture.Apps ||
|
||||
response.Observed.Disks != fixture.Disks ||
|
||||
response.Observed.RecoveryArtifacts != fixture.RecoveryArtifacts {
|
||||
t.Fatalf("unexpected truenas observed summary: got %+v want fixture %+v", response.Observed, fixture)
|
||||
}
|
||||
if response.Observed.CollectedAt == nil || !response.Observed.CollectedAt.Equal(fixture.CollectedAt) {
|
||||
t.Fatalf("expected truenas observed collectedAt %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Observed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("vmware", func(t *testing.T) {
|
||||
fixture := mock.DefaultVMwareConnectionFixture()
|
||||
if fixture.CollectedAt.IsZero() {
|
||||
t.Fatal("expected canonical vmware mock fixture timestamp")
|
||||
}
|
||||
|
||||
handler, _ := newVMwareHandlersForTest(t)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleList(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var responses []vmwareConnectionResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
|
||||
t.Fatalf("decode vmware mock list response: %v", err)
|
||||
}
|
||||
if len(responses) != 1 {
|
||||
t.Fatalf("expected 1 mock vmware connection, got %d", len(responses))
|
||||
}
|
||||
|
||||
response := responses[0]
|
||||
if response.ID != fixture.ID || response.Name != fixture.Name || response.Host != fixture.Host || response.Port != fixture.Port || response.Username != fixture.Username {
|
||||
t.Fatalf("unexpected vmware mock connection metadata: got %+v want fixture %+v", response.VMwareVCenterInstance, fixture)
|
||||
}
|
||||
if response.Password != "********" {
|
||||
t.Fatalf("expected redacted vmware password, got %q", response.Password)
|
||||
}
|
||||
if response.Poll == nil || response.Poll.IntervalSeconds != fixture.PollIntervalSeconds {
|
||||
t.Fatalf("expected vmware poll interval %d, got %+v", fixture.PollIntervalSeconds, response.Poll)
|
||||
}
|
||||
if response.Poll.LastSuccessAt == nil || !response.Poll.LastSuccessAt.Equal(fixture.CollectedAt) {
|
||||
t.Fatalf("expected vmware last success at %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Poll)
|
||||
}
|
||||
if response.Observed == nil {
|
||||
t.Fatal("expected vmware observed summary")
|
||||
}
|
||||
if response.Observed.Hosts != fixture.Hosts ||
|
||||
response.Observed.VMs != fixture.VMs ||
|
||||
response.Observed.Datastores != fixture.Datastores ||
|
||||
response.Observed.VIRelease != fixture.VIRelease {
|
||||
t.Fatalf("unexpected vmware observed summary: got %+v want fixture %+v", response.Observed, fixture)
|
||||
}
|
||||
if response.Observed.CollectedAt == nil || !response.Observed.CollectedAt.Equal(fixture.CollectedAt) {
|
||||
t.Fatalf("expected vmware observed collectedAt %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Observed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestContract_VMwareConnectionListCarriesDegradedObservedSummary(t *testing.T) {
|
||||
setVMwareFeatureForTest(t, true)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +1,78 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
|
||||
)
|
||||
|
||||
const defaultMockPlatformPollIntervalSeconds = 60
|
||||
|
||||
func mockTrueNASConnectionResponses() []trueNASConnectionResponse {
|
||||
fixtures := truenas.DefaultFixtures()
|
||||
collectedAt := fixtures.CollectedAt
|
||||
if collectedAt.IsZero() {
|
||||
collectedAt = fixtures.System.CollectedAt
|
||||
}
|
||||
fixture := mock.DefaultTrueNASConnectionFixture()
|
||||
|
||||
instance := config.TrueNASInstance{
|
||||
ID: "truenas-mock-1",
|
||||
Name: "Archive NAS",
|
||||
Host: strings.TrimSpace(fixtures.System.Hostname),
|
||||
Port: 443,
|
||||
APIKey: "mock-truenas-api-key",
|
||||
UseHTTPS: true,
|
||||
Enabled: true,
|
||||
PollIntervalSecs: defaultMockPlatformPollIntervalSeconds,
|
||||
ID: fixture.ID,
|
||||
Name: fixture.Name,
|
||||
Host: fixture.Host,
|
||||
Port: fixture.Port,
|
||||
APIKey: fixture.APIKey,
|
||||
UseHTTPS: fixture.UseHTTPS,
|
||||
Enabled: fixture.Enabled,
|
||||
PollIntervalSecs: fixture.PollIntervalSeconds,
|
||||
}
|
||||
instance.ApplyDefaults()
|
||||
|
||||
observed := &monitoring.TrueNASConnectionObservedSummary{
|
||||
Host: strings.TrimSpace(fixtures.System.Hostname),
|
||||
ResourceID: strings.TrimSpace(fixtures.System.Hostname),
|
||||
Systems: 1,
|
||||
StoragePools: len(fixtures.Pools),
|
||||
Datasets: len(fixtures.Datasets),
|
||||
Apps: len(fixtures.Apps),
|
||||
Disks: len(fixtures.Disks),
|
||||
RecoveryArtifacts: len(fixtures.ZFSSnapshots) + len(fixtures.ReplicationTasks),
|
||||
CollectedAt: mockPlatformTimePointer(collectedAt),
|
||||
Host: fixture.Host,
|
||||
ResourceID: fixture.ResourceID,
|
||||
Systems: fixture.Systems,
|
||||
StoragePools: fixture.StoragePools,
|
||||
Datasets: fixture.Datasets,
|
||||
Apps: fixture.Apps,
|
||||
Disks: fixture.Disks,
|
||||
RecoveryArtifacts: fixture.RecoveryArtifacts,
|
||||
CollectedAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
}
|
||||
|
||||
return []trueNASConnectionResponse{{
|
||||
TrueNASInstance: instance.Redacted(),
|
||||
Poll: &monitoring.TrueNASConnectionPollStatus{
|
||||
IntervalSeconds: instance.EffectivePollIntervalSecs(),
|
||||
LastAttemptAt: mockPlatformTimePointer(collectedAt),
|
||||
LastSuccessAt: mockPlatformTimePointer(collectedAt),
|
||||
LastAttemptAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
LastSuccessAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
},
|
||||
Observed: observed,
|
||||
}}
|
||||
}
|
||||
|
||||
func mockVMwareConnectionResponses() []vmwareConnectionResponse {
|
||||
snapshot := vmware.DefaultFixtures()
|
||||
collectedAt := snapshot.CollectedAt
|
||||
fixture := mock.DefaultVMwareConnectionFixture()
|
||||
|
||||
instance := config.VMwareVCenterInstance{
|
||||
ID: strings.TrimSpace(snapshot.ConnectionID),
|
||||
Name: strings.TrimSpace(snapshot.ConnectionName),
|
||||
Host: strings.TrimSpace(snapshot.VCenterHost),
|
||||
Port: 443,
|
||||
Username: "administrator@vsphere.local",
|
||||
Password: "mock-vcenter-password",
|
||||
Enabled: true,
|
||||
ID: fixture.ID,
|
||||
Name: fixture.Name,
|
||||
Host: fixture.Host,
|
||||
Port: fixture.Port,
|
||||
Username: fixture.Username,
|
||||
Password: fixture.Password,
|
||||
Enabled: fixture.Enabled,
|
||||
}
|
||||
instance.ApplyDefaults()
|
||||
|
||||
return []vmwareConnectionResponse{{
|
||||
VMwareVCenterInstance: instance.Redacted(),
|
||||
Poll: &monitoring.VMwareConnectionPollStatus{
|
||||
IntervalSeconds: defaultMockPlatformPollIntervalSeconds,
|
||||
LastAttemptAt: mockPlatformTimePointer(collectedAt),
|
||||
LastSuccessAt: mockPlatformTimePointer(collectedAt),
|
||||
IntervalSeconds: fixture.PollIntervalSeconds,
|
||||
LastAttemptAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
LastSuccessAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
},
|
||||
Observed: &monitoring.VMwareConnectionObservedSummary{
|
||||
CollectedAt: mockPlatformTimePointer(collectedAt),
|
||||
Hosts: len(snapshot.Hosts),
|
||||
VMs: len(snapshot.VMs),
|
||||
Datastores: len(snapshot.Datastores),
|
||||
VIRelease: strings.TrimSpace(snapshot.VIRelease),
|
||||
CollectedAt: mockPlatformTimePointer(fixture.CollectedAt),
|
||||
Hosts: fixture.Hosts,
|
||||
VMs: fixture.VMs,
|
||||
Datastores: fixture.Datastores,
|
||||
VIRelease: fixture.VIRelease,
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3546,7 +3546,7 @@ func TestResourceListWithoutSupplementalProvider(t *testing.T) {
|
|||
|
||||
func TestSupplementalSnapshotOwnedSources_TrueNASProviders(t *testing.T) {
|
||||
sources := supplementalSnapshotOwnedSources(map[unified.DataSource]SupplementalRecordsProvider{
|
||||
unified.SourceTrueNAS: trueNASRecordsAdapter{},
|
||||
unified.SourceTrueNAS: mockSupplementalRecordsAdapter{source: unified.SourceTrueNAS},
|
||||
}, "default")
|
||||
|
||||
if len(sources) != 1 || sources[0] != unified.SourceTrueNAS {
|
||||
|
|
|
|||
|
|
@ -1262,8 +1262,8 @@ func (r *Router) syncPlatformSupplementalProviders(mockEnabled bool) {
|
|||
truenas.SetFeatureEnabled(true)
|
||||
vmware.SetFeatureEnabled(true)
|
||||
|
||||
trueNASAdapter := trueNASRecordsAdapter{provider: truenas.NewDefaultProvider()}
|
||||
vmwareAdapter := vmwareRecordsAdapter{provider: vmware.NewDefaultProvider()}
|
||||
trueNASAdapter := mockSupplementalRecordsAdapter{source: unifiedresources.SourceTrueNAS}
|
||||
vmwareAdapter := mockSupplementalRecordsAdapter{source: unifiedresources.SourceVMware}
|
||||
|
||||
if r.resourceHandlers != nil {
|
||||
r.resourceHandlers.SetSupplementalRecordsProvider(unifiedresources.SourceTrueNAS, trueNASAdapter)
|
||||
|
|
@ -1274,6 +1274,9 @@ func (r *Router) syncPlatformSupplementalProviders(mockEnabled bool) {
|
|||
return
|
||||
}
|
||||
|
||||
truenas.ResetFeatureEnabledFromEnv()
|
||||
vmware.ResetFeatureEnabledFromEnv()
|
||||
|
||||
if r.resourceHandlers != nil {
|
||||
r.resourceHandlers.SetSupplementalRecordsProvider(unifiedresources.SourceTrueNAS, r.trueNASPoller)
|
||||
r.resourceHandlers.SetSupplementalRecordsProvider(unifiedresources.SourceVMware, r.vmwarePoller)
|
||||
|
|
@ -9063,66 +9066,35 @@ func (w *knowledgeStoreProviderWrapper) GetKnowledge(resourceID string, category
|
|||
return result
|
||||
}
|
||||
|
||||
// trueNASRecordsAdapter wraps a truenas.Provider to satisfy SupplementalRecordsProvider.
|
||||
type trueNASRecordsAdapter struct {
|
||||
provider *truenas.Provider
|
||||
type mockSupplementalRecordsAdapter struct {
|
||||
source unifiedresources.DataSource
|
||||
}
|
||||
|
||||
func (a trueNASRecordsAdapter) GetCurrentRecords() []unifiedresources.IngestRecord {
|
||||
// Legacy behavior: treat this mock adapter as default-org scoped.
|
||||
func (a mockSupplementalRecordsAdapter) GetCurrentRecords() []unifiedresources.IngestRecord {
|
||||
return a.GetCurrentRecordsForOrg("default")
|
||||
}
|
||||
|
||||
func (a trueNASRecordsAdapter) GetCurrentRecordsForOrg(orgID string) []unifiedresources.IngestRecord {
|
||||
func (a mockSupplementalRecordsAdapter) GetCurrentRecordsForOrg(orgID string) []unifiedresources.IngestRecord {
|
||||
if strings.TrimSpace(orgID) != "" && strings.TrimSpace(orgID) != "default" {
|
||||
return nil
|
||||
}
|
||||
if a.provider == nil {
|
||||
return nil
|
||||
}
|
||||
return a.provider.Records()
|
||||
return mock.SupplementalRecords(a.source)
|
||||
}
|
||||
|
||||
func (a trueNASRecordsAdapter) SupplementalRecords(_ *monitoring.Monitor, orgID string) []unifiedresources.IngestRecord {
|
||||
func (a mockSupplementalRecordsAdapter) SupplementalRecords(_ *monitoring.Monitor, orgID string) []unifiedresources.IngestRecord {
|
||||
return a.GetCurrentRecordsForOrg(orgID)
|
||||
}
|
||||
|
||||
func (a trueNASRecordsAdapter) SnapshotOwnedSources() []unifiedresources.DataSource {
|
||||
return []unifiedresources.DataSource{unifiedresources.SourceTrueNAS}
|
||||
}
|
||||
|
||||
func (a trueNASRecordsAdapter) SnapshotOwnedSourcesForOrg(string) []unifiedresources.DataSource {
|
||||
return []unifiedresources.DataSource{unifiedresources.SourceTrueNAS}
|
||||
}
|
||||
|
||||
type vmwareRecordsAdapter struct {
|
||||
provider *vmware.Provider
|
||||
}
|
||||
|
||||
func (a vmwareRecordsAdapter) GetCurrentRecords() []unifiedresources.IngestRecord {
|
||||
return a.GetCurrentRecordsForOrg("default")
|
||||
}
|
||||
|
||||
func (a vmwareRecordsAdapter) GetCurrentRecordsForOrg(orgID string) []unifiedresources.IngestRecord {
|
||||
if strings.TrimSpace(orgID) != "" && strings.TrimSpace(orgID) != "default" {
|
||||
func (a mockSupplementalRecordsAdapter) SnapshotOwnedSources() []unifiedresources.DataSource {
|
||||
normalized := normalizeDataSourceAlias(a.source)
|
||||
if normalized == "" {
|
||||
return nil
|
||||
}
|
||||
if a.provider == nil {
|
||||
return nil
|
||||
}
|
||||
return a.provider.Records()
|
||||
return []unifiedresources.DataSource{normalized}
|
||||
}
|
||||
|
||||
func (a vmwareRecordsAdapter) SupplementalRecords(_ *monitoring.Monitor, orgID string) []unifiedresources.IngestRecord {
|
||||
return a.GetCurrentRecordsForOrg(orgID)
|
||||
}
|
||||
|
||||
func (a vmwareRecordsAdapter) SnapshotOwnedSources() []unifiedresources.DataSource {
|
||||
return []unifiedresources.DataSource{unifiedresources.SourceVMware}
|
||||
}
|
||||
|
||||
func (a vmwareRecordsAdapter) SnapshotOwnedSourcesForOrg(string) []unifiedresources.DataSource {
|
||||
return []unifiedresources.DataSource{unifiedresources.SourceVMware}
|
||||
func (a mockSupplementalRecordsAdapter) SnapshotOwnedSourcesForOrg(string) []unifiedresources.DataSource {
|
||||
return a.SnapshotOwnedSources()
|
||||
}
|
||||
|
||||
// trigger rebuild Fri Jan 16 10:52:41 UTC 2026
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import (
|
|||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
|
||||
unified "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
|
||||
)
|
||||
|
||||
func TestRouterMockMode_SeedsTrueNASAndVMwareSupplementalResources(t *testing.T) {
|
||||
|
|
@ -67,3 +69,39 @@ func TestRouterMockMode_SeedsTrueNASAndVMwareSupplementalResources(t *testing.T)
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterMockMode_RestoresPlatformFeatureFlagsAfterDisable(t *testing.T) {
|
||||
t.Setenv(truenas.FeatureTrueNAS, "false")
|
||||
t.Setenv(vmware.FeatureVMware, "false")
|
||||
|
||||
previousTrueNAS := truenas.IsFeatureEnabled()
|
||||
previousVMware := vmware.IsFeatureEnabled()
|
||||
truenas.ResetFeatureEnabledFromEnv()
|
||||
vmware.ResetFeatureEnabledFromEnv()
|
||||
t.Cleanup(func() {
|
||||
truenas.SetFeatureEnabled(previousTrueNAS)
|
||||
vmware.SetFeatureEnabled(previousVMware)
|
||||
})
|
||||
|
||||
cfg := &config.Config{DataPath: t.TempDir()}
|
||||
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
|
||||
t.Cleanup(func() {
|
||||
router.shutdownBackgroundWorkers()
|
||||
})
|
||||
|
||||
router.syncPlatformSupplementalProviders(true)
|
||||
if !truenas.IsFeatureEnabled() {
|
||||
t.Fatal("expected mock mode to force-enable TrueNAS feature flag")
|
||||
}
|
||||
if !vmware.IsFeatureEnabled() {
|
||||
t.Fatal("expected mock mode to force-enable VMware feature flag")
|
||||
}
|
||||
|
||||
router.syncPlatformSupplementalProviders(false)
|
||||
if truenas.IsFeatureEnabled() {
|
||||
t.Fatal("expected disabling mock mode to restore TrueNAS feature flag from env")
|
||||
}
|
||||
if vmware.IsFeatureEnabled() {
|
||||
t.Fatal("expected disabling mock mode to restore VMware feature flag from env")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
174
internal/mock/platform_fixtures.go
Normal file
174
internal/mock/platform_fixtures.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
|
||||
)
|
||||
|
||||
const DefaultPlatformPollIntervalSeconds = 60
|
||||
|
||||
type PlatformFixtures struct {
|
||||
TrueNAS truenas.FixtureSnapshot
|
||||
VMware vmware.InventorySnapshot
|
||||
}
|
||||
|
||||
type TrueNASConnectionFixture struct {
|
||||
ID string
|
||||
Name string
|
||||
Host string
|
||||
Port int
|
||||
APIKey string
|
||||
UseHTTPS bool
|
||||
Enabled bool
|
||||
PollIntervalSeconds int
|
||||
CollectedAt time.Time
|
||||
ResourceID string
|
||||
Systems int
|
||||
StoragePools int
|
||||
Datasets int
|
||||
Apps int
|
||||
Disks int
|
||||
RecoveryArtifacts int
|
||||
}
|
||||
|
||||
type VMwareConnectionFixture struct {
|
||||
ID string
|
||||
Name string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Enabled bool
|
||||
PollIntervalSeconds int
|
||||
CollectedAt time.Time
|
||||
Hosts int
|
||||
VMs int
|
||||
Datastores int
|
||||
VIRelease string
|
||||
}
|
||||
|
||||
func DefaultPlatformFixtures() PlatformFixtures {
|
||||
return PlatformFixtures{
|
||||
TrueNAS: truenas.DefaultFixtures(),
|
||||
VMware: vmware.DefaultFixtures(),
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultTrueNASConnectionFixture() TrueNASConnectionFixture {
|
||||
fixtures := DefaultPlatformFixtures().TrueNAS
|
||||
collectedAt := trueNASCollectedAt(fixtures)
|
||||
host := strings.TrimSpace(fixtures.System.Hostname)
|
||||
|
||||
return TrueNASConnectionFixture{
|
||||
ID: "truenas-mock-1",
|
||||
Name: "Archive NAS",
|
||||
Host: host,
|
||||
Port: 443,
|
||||
APIKey: "mock-truenas-api-key",
|
||||
UseHTTPS: true,
|
||||
Enabled: true,
|
||||
PollIntervalSeconds: DefaultPlatformPollIntervalSeconds,
|
||||
CollectedAt: collectedAt,
|
||||
ResourceID: host,
|
||||
Systems: 1,
|
||||
StoragePools: len(fixtures.Pools),
|
||||
Datasets: len(fixtures.Datasets),
|
||||
Apps: len(fixtures.Apps),
|
||||
Disks: len(fixtures.Disks),
|
||||
RecoveryArtifacts: len(fixtures.ZFSSnapshots) + len(fixtures.ReplicationTasks),
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultVMwareConnectionFixture() VMwareConnectionFixture {
|
||||
fixtures := DefaultPlatformFixtures().VMware
|
||||
|
||||
return VMwareConnectionFixture{
|
||||
ID: strings.TrimSpace(fixtures.ConnectionID),
|
||||
Name: strings.TrimSpace(fixtures.ConnectionName),
|
||||
Host: strings.TrimSpace(fixtures.VCenterHost),
|
||||
Port: 443,
|
||||
Username: "administrator@vsphere.local",
|
||||
Password: "mock-vcenter-password",
|
||||
Enabled: true,
|
||||
PollIntervalSeconds: DefaultPlatformPollIntervalSeconds,
|
||||
CollectedAt: fixtures.CollectedAt,
|
||||
Hosts: len(fixtures.Hosts),
|
||||
VMs: len(fixtures.VMs),
|
||||
Datastores: len(fixtures.Datastores),
|
||||
VIRelease: strings.TrimSpace(fixtures.VIRelease),
|
||||
}
|
||||
}
|
||||
|
||||
func SupplementalRecords(source unifiedresources.DataSource) []unifiedresources.IngestRecord {
|
||||
switch normalizePlatformSource(source) {
|
||||
case unifiedresources.SourceTrueNAS:
|
||||
return truenas.FixtureRecords(DefaultPlatformFixtures().TrueNAS)
|
||||
case unifiedresources.SourceVMware:
|
||||
return vmware.FixtureRecords(DefaultPlatformFixtures().VMware)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PlatformOwnedSources() []unifiedresources.DataSource {
|
||||
return []unifiedresources.DataSource{
|
||||
unifiedresources.SourceTrueNAS,
|
||||
unifiedresources.SourceVMware,
|
||||
}
|
||||
}
|
||||
|
||||
func UnifiedResourceSnapshot() ([]unifiedresources.Resource, time.Time) {
|
||||
if !IsMockEnabled() {
|
||||
return nil, time.Time{}
|
||||
}
|
||||
|
||||
fixtures := DefaultPlatformFixtures()
|
||||
snapshot := GetMockState()
|
||||
|
||||
registry := unifiedresources.NewRegistry(nil)
|
||||
registry.IngestSnapshot(unifiedresources.SnapshotWithoutSources(snapshot, PlatformOwnedSources()))
|
||||
for _, source := range PlatformOwnedSources() {
|
||||
records := SupplementalRecords(source)
|
||||
if len(records) == 0 {
|
||||
continue
|
||||
}
|
||||
registry.IngestRecords(source, records)
|
||||
}
|
||||
|
||||
freshness := snapshot.LastUpdate
|
||||
for _, candidate := range []time.Time{
|
||||
trueNASCollectedAt(fixtures.TrueNAS),
|
||||
fixtures.VMware.CollectedAt,
|
||||
} {
|
||||
if candidate.IsZero() {
|
||||
continue
|
||||
}
|
||||
if freshness.IsZero() || candidate.After(freshness) {
|
||||
freshness = candidate
|
||||
}
|
||||
}
|
||||
|
||||
return registry.List(), freshness
|
||||
}
|
||||
|
||||
func trueNASCollectedAt(fixtures truenas.FixtureSnapshot) time.Time {
|
||||
if !fixtures.CollectedAt.IsZero() {
|
||||
return fixtures.CollectedAt
|
||||
}
|
||||
return fixtures.System.CollectedAt
|
||||
}
|
||||
|
||||
func normalizePlatformSource(source unifiedresources.DataSource) unifiedresources.DataSource {
|
||||
switch strings.ToLower(strings.TrimSpace(string(source))) {
|
||||
case "truenas":
|
||||
return unifiedresources.SourceTrueNAS
|
||||
case "vmware", "vmware-vsphere":
|
||||
return unifiedresources.SourceVMware
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
44
internal/mock/platform_fixtures_test.go
Normal file
44
internal/mock/platform_fixtures_test.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
)
|
||||
|
||||
func TestUnifiedResourceSnapshotIncludesPlatformFixtures(t *testing.T) {
|
||||
previous := IsMockEnabled()
|
||||
SetEnabled(true)
|
||||
t.Cleanup(func() { SetEnabled(previous) })
|
||||
|
||||
resources, freshness := UnifiedResourceSnapshot()
|
||||
if len(resources) == 0 {
|
||||
t.Fatal("expected unified resources in mock mode")
|
||||
}
|
||||
if freshness.IsZero() {
|
||||
t.Fatal("expected non-zero freshness for mock unified resources")
|
||||
}
|
||||
|
||||
wantNames := map[string]bool{
|
||||
"truenas-main": false,
|
||||
"esxi-01.lab.local": false,
|
||||
"orders-api-01": false,
|
||||
}
|
||||
for _, resource := range resources {
|
||||
if _, ok := wantNames[resource.Name]; ok {
|
||||
wantNames[resource.Name] = true
|
||||
}
|
||||
}
|
||||
for name, found := range wantNames {
|
||||
if !found {
|
||||
t.Fatalf("expected mock unified resources to include %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupplementalRecordsNormalizesVMwareAlias(t *testing.T) {
|
||||
records := SupplementalRecords(unifiedresources.DataSource("vmware-vsphere"))
|
||||
if len(records) == 0 {
|
||||
t.Fatal("expected records for vmware-vsphere alias")
|
||||
}
|
||||
}
|
||||
|
|
@ -376,12 +376,22 @@ func generateMockRecoveryPoints() []recovery.RecoveryPoint {
|
|||
}
|
||||
|
||||
// TrueNAS: 3 dataset subjects with multiple points over time.
|
||||
truenasConnID := "truenas-mock-1"
|
||||
truenasHost := "truenas.local"
|
||||
truenasDatasets := []string{
|
||||
"tank/apps/postgres",
|
||||
"tank/apps/minio",
|
||||
"tank/media/photos",
|
||||
truenasConnection := DefaultTrueNASConnectionFixture()
|
||||
truenasConnID := truenasConnection.ID
|
||||
truenasHost := truenasConnection.Host
|
||||
truenasDatasets := make([]string, 0, 3)
|
||||
for _, dataset := range DefaultPlatformFixtures().TrueNAS.Datasets {
|
||||
name := strings.TrimSpace(dataset.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
truenasDatasets = append(truenasDatasets, name)
|
||||
if len(truenasDatasets) == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(truenasDatasets) == 0 {
|
||||
truenasDatasets = []string{"tank/apps", "tank/media", "archive/backups"}
|
||||
}
|
||||
|
||||
// ZFS snapshots (snapshot / snapshot): success points with meaningful details.
|
||||
|
|
|
|||
|
|
@ -260,6 +260,23 @@ func TestUnifiedAppContainerMetricsUseCanonicalGuestHistoryPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMockUnifiedStateViewUsesCanonicalMockFixtureGraph(t *testing.T) {
|
||||
data, err := os.ReadFile("monitor.go")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read monitor.go: %v", err)
|
||||
}
|
||||
source := string(data)
|
||||
requiredSnippets := []string{
|
||||
"resources, freshness := mock.UnifiedResourceSnapshot()",
|
||||
"return monitorUnifiedStateViewFromResources(resources, freshness)",
|
||||
}
|
||||
for _, snippet := range requiredSnippets {
|
||||
if !strings.Contains(source, snippet) {
|
||||
t.Fatalf("monitor.go must contain %q", snippet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifiedAgentMetricsUseCanonicalHostHistoryPath(t *testing.T) {
|
||||
data, err := os.ReadFile("monitor.go")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ 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"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -876,13 +875,13 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Seed TrueNAS pool/dataset disk-usage metrics
|
||||
fixtures := truenas.DefaultFixtures()
|
||||
log.Debug().Int("pools", len(fixtures.Pools)).Int("datasets", len(fixtures.Datasets)).Msg("mock seeding: processing TrueNAS fixtures")
|
||||
platformFixtures := mock.DefaultPlatformFixtures()
|
||||
trueNASFixtures := platformFixtures.TrueNAS
|
||||
log.Debug().Int("pools", len(trueNASFixtures.Pools)).Int("datasets", len(trueNASFixtures.Datasets)).Msg("mock seeding: processing TrueNAS fixtures")
|
||||
|
||||
// System host: aggregated disk usage across all pools
|
||||
totalCap, totalUsed := int64(0), int64(0)
|
||||
for _, pool := range fixtures.Pools {
|
||||
for _, pool := range trueNASFixtures.Pools {
|
||||
totalCap += pool.TotalBytes
|
||||
totalUsed += pool.UsedBytes
|
||||
}
|
||||
|
|
@ -890,10 +889,10 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
if totalCap > 0 {
|
||||
systemDisk = float64(totalUsed) / float64(totalCap) * 100
|
||||
}
|
||||
systemKey := "system:" + fixtures.System.Hostname
|
||||
recordGuest(systemKey, "truenas", fixtures.System.Hostname, 0, 0, systemDisk, 0, 0, 0, 0, true, false, false)
|
||||
systemKey := "system:" + trueNASFixtures.System.Hostname
|
||||
recordGuest(systemKey, "truenas", trueNASFixtures.System.Hostname, 0, 0, systemDisk, 0, 0, 0, 0, true, false, false)
|
||||
|
||||
for _, pool := range fixtures.Pools {
|
||||
for _, pool := range trueNASFixtures.Pools {
|
||||
poolKey := "pool:" + pool.Name
|
||||
diskPercent := float64(0)
|
||||
if pool.TotalBytes > 0 {
|
||||
|
|
@ -910,7 +909,7 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
queueMetric("pool", pool.Name, "disk", diskPercent, now)
|
||||
}
|
||||
|
||||
for _, dataset := range fixtures.Datasets {
|
||||
for _, dataset := range trueNASFixtures.Datasets {
|
||||
dsKey := "dataset:" + dataset.Name
|
||||
totalBytes := dataset.UsedBytes + dataset.AvailBytes
|
||||
diskPercent := float64(0)
|
||||
|
|
@ -928,7 +927,7 @@ func seedMockMetricsHistory(mh *MetricsHistory, ms *metrics.Store, state models.
|
|||
queueMetric("dataset", dataset.Name, "disk", diskPercent, now)
|
||||
}
|
||||
|
||||
vmwareFixtures := vmware.DefaultFixtures()
|
||||
vmwareFixtures := platformFixtures.VMware
|
||||
vmwareDatastoreUsage := vmwareDatastoreUsageByID(vmwareFixtures.Datastores)
|
||||
log.Debug().
|
||||
Int("hosts", len(vmwareFixtures.Hosts)).
|
||||
|
|
@ -1008,7 +1007,7 @@ 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 := truenas.DefaultFixtures()
|
||||
fixtures := mock.DefaultPlatformFixtures().TrueNAS
|
||||
|
||||
totalCap, totalUsed := int64(0), int64(0)
|
||||
for _, pool := range fixtures.Pools {
|
||||
|
|
@ -1049,7 +1048,7 @@ func recordTrueNASFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time
|
|||
}
|
||||
|
||||
func recordVMwareFixturesMetrics(mh *MetricsHistory, ms *metrics.Store, ts time.Time) {
|
||||
snapshot := vmware.DefaultFixtures()
|
||||
snapshot := mock.DefaultPlatformFixtures().VMware
|
||||
datastoreUsage := vmwareDatastoreUsageByID(snapshot.Datastores)
|
||||
|
||||
for _, host := range snapshot.Hosts {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package monitoring
|
|||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -310,6 +311,51 @@ func TestSeedMockMetricsHistory_SeedsVMwareMetricsStore(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSeedMockMetricsHistory_SeedsTrueNASMetricsStore(t *testing.T) {
|
||||
now := time.Now()
|
||||
state := models.StateSnapshot{}
|
||||
fixtures := mock.DefaultPlatformFixtures().TrueNAS
|
||||
|
||||
if strings.TrimSpace(fixtures.System.Hostname) == "" {
|
||||
t.Fatal("expected canonical truenas system hostname fixture")
|
||||
}
|
||||
if len(fixtures.Datasets) == 0 {
|
||||
t.Fatal("expected canonical truenas dataset fixtures")
|
||||
}
|
||||
|
||||
cfg := metrics.DefaultConfig(t.TempDir())
|
||||
cfg.RetentionRaw = 90 * 24 * time.Hour
|
||||
cfg.RetentionMinute = 90 * 24 * time.Hour
|
||||
cfg.RetentionHourly = 90 * 24 * time.Hour
|
||||
cfg.RetentionDaily = 90 * 24 * time.Hour
|
||||
cfg.WriteBufferSize = 500
|
||||
|
||||
store, err := metrics.NewStore(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create metrics store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
mh := NewMetricsHistory(1000, 7*24*time.Hour)
|
||||
seedMockMetricsHistory(mh, store, state, 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 {
|
||||
t.Fatalf("failed to query TrueNAS system disk metrics: %v", err)
|
||||
}
|
||||
if len(systemPoints) == 0 {
|
||||
t.Fatal("expected metrics store to have seeded TrueNAS system disk points")
|
||||
}
|
||||
|
||||
datasetPoints, err := store.Query("dataset", fixtures.Datasets[0].Name, "disk", now.Add(-7*24*time.Hour), now, 3600)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query TrueNAS dataset disk metrics: %v", err)
|
||||
}
|
||||
if len(datasetPoints) == 0 {
|
||||
t.Fatal("expected metrics store to have seeded TrueNAS dataset disk points")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartMockMetricsSampler_DoesNotClearExistingMetricsStoreData(t *testing.T) {
|
||||
t.Setenv("PULSE_MOCK_NODES", "1")
|
||||
t.Setenv("PULSE_MOCK_VMS_PER_NODE", "0")
|
||||
|
|
|
|||
|
|
@ -4004,6 +4004,10 @@ func (m *Monitor) currentUnifiedStateView() monitorUnifiedStateView {
|
|||
}
|
||||
|
||||
if mock.IsMockEnabled() {
|
||||
resources, freshness := mock.UnifiedResourceSnapshot()
|
||||
if len(resources) > 0 || !freshness.IsZero() {
|
||||
return monitorUnifiedStateViewFromResources(resources, freshness)
|
||||
}
|
||||
return monitorUnifiedStateViewFromSnapshot(m.GetState())
|
||||
}
|
||||
|
||||
|
|
@ -4058,8 +4062,8 @@ func unifiedResourceFreshness(store ResourceStoreInterface, state *models.State)
|
|||
}
|
||||
|
||||
// UnifiedResourceSnapshot returns a canonical unified-resource seed plus the
|
||||
// associated freshness marker. It respects mock mode by falling back to the
|
||||
// current mock-aware state snapshot instead of the live resource store.
|
||||
// associated freshness marker. In mock mode it returns the shared mock
|
||||
// unified-resource fixture graph rather than the live resource store.
|
||||
func (m *Monitor) UnifiedResourceSnapshot() ([]unifiedresources.Resource, time.Time) {
|
||||
view := m.currentUnifiedStateView()
|
||||
return view.resources, view.freshness
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ func TestMonitorUnifiedResourceSnapshotPrefersStoreFreshness(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMonitorGetUnifiedReadStateOrSnapshotUsesMockSnapshotInsteadOfStore(t *testing.T) {
|
||||
func TestMonitorGetUnifiedReadStateOrSnapshotUsesCanonicalMockUnifiedResources(t *testing.T) {
|
||||
mock.SetEnabled(true)
|
||||
t.Cleanup(func() { mock.SetEnabled(false) })
|
||||
|
||||
|
|
@ -207,7 +207,14 @@ func TestMonitorGetUnifiedReadStateOrSnapshotUsesMockSnapshotInsteadOfStore(t *t
|
|||
t.Fatalf("expected read-state adapter with GetAll, got %T", readState)
|
||||
}
|
||||
|
||||
if hasUnifiedResource(getter.GetAll(), "store-only-resource") {
|
||||
resources := getter.GetAll()
|
||||
if hasUnifiedResource(resources, "store-only-resource") {
|
||||
t.Fatal("expected mock-mode read-state to ignore live resource store data")
|
||||
}
|
||||
if !hasUnifiedResourceName(resources, "truenas-main") {
|
||||
t.Fatalf("expected mock-mode read-state to include TrueNAS mock resources, got %#v", resources)
|
||||
}
|
||||
if !hasUnifiedResourceName(resources, "esxi-01.lab.local") {
|
||||
t.Fatalf("expected mock-mode read-state to include VMware mock resources, got %#v", resources)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
internal/truenas/fixture_records_test.go
Normal file
23
internal/truenas/fixture_records_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package truenas
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFixtureRecordsIgnoreFeatureFlag(t *testing.T) {
|
||||
previous := IsFeatureEnabled()
|
||||
SetFeatureEnabled(false)
|
||||
t.Cleanup(func() { SetFeatureEnabled(previous) })
|
||||
|
||||
records := FixtureRecords(DefaultFixtures())
|
||||
if len(records) == 0 {
|
||||
t.Fatal("expected fixture records even when feature flag is disabled")
|
||||
}
|
||||
|
||||
systemID := systemSourceID("truenas-main")
|
||||
for _, record := range records {
|
||||
if record.SourceID == systemID {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("expected fixture records to include system source %q", systemID)
|
||||
}
|
||||
|
|
@ -40,6 +40,12 @@ func SetFeatureEnabled(enabled bool) {
|
|||
featureTrueNASEnabled.Store(enabled)
|
||||
}
|
||||
|
||||
// ResetFeatureEnabledFromEnv restores the feature flag from the current
|
||||
// environment configuration.
|
||||
func ResetFeatureEnabledFromEnv() {
|
||||
featureTrueNASEnabled.Store(parseFeatureEnabled(os.Getenv(FeatureTrueNAS)))
|
||||
}
|
||||
|
||||
// Fetcher loads a TrueNAS snapshot from a concrete source.
|
||||
type Fetcher interface {
|
||||
Fetch(ctx context.Context) (*FixtureSnapshot, error)
|
||||
|
|
@ -415,20 +421,36 @@ func (p *Provider) Snapshot() *FixtureSnapshot {
|
|||
return snapshot
|
||||
}
|
||||
|
||||
// FixtureRecords projects a TrueNAS fixture snapshot into canonical unified
|
||||
// resource ingest records without consulting the runtime feature flag.
|
||||
func FixtureRecords(snapshot FixtureSnapshot) []unifiedresources.IngestRecord {
|
||||
return truenasRecordsFromSnapshot(&snapshot, nil)
|
||||
}
|
||||
|
||||
// Records returns unified records if the feature flag is enabled.
|
||||
func (p *Provider) Records() []unifiedresources.IngestRecord {
|
||||
if p == nil || !IsFeatureEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshot := p.Snapshot()
|
||||
return truenasRecordsFromSnapshot(p.Snapshot(), p.now)
|
||||
}
|
||||
|
||||
func truenasRecordsFromSnapshot(snapshot *FixtureSnapshot, now func() time.Time) []unifiedresources.IngestRecord {
|
||||
if snapshot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
collectedAt := snapshot.CollectedAt
|
||||
if collectedAt.IsZero() {
|
||||
collectedAt = p.now()
|
||||
collectedAt = snapshot.System.CollectedAt
|
||||
}
|
||||
if collectedAt.IsZero() {
|
||||
if now != nil {
|
||||
collectedAt = now().UTC()
|
||||
} else {
|
||||
collectedAt = time.Now().UTC()
|
||||
}
|
||||
}
|
||||
systemSourceID := systemSourceID(snapshot.System.Hostname)
|
||||
systemAssessment := assessSystemStorage(snapshot)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ func SetFeatureEnabled(enabled bool) {
|
|||
featureVMwareEnabled.Store(enabled)
|
||||
}
|
||||
|
||||
// ResetFeatureEnabledFromEnv restores the feature flag from the current
|
||||
// environment configuration.
|
||||
func ResetFeatureEnabledFromEnv() {
|
||||
featureVMwareEnabled.Store(parseFeatureEnabled(os.Getenv(FeatureVMware)))
|
||||
}
|
||||
|
||||
func parseFeatureEnabled(raw string) bool {
|
||||
switch strings.TrimSpace(strings.ToLower(raw)) {
|
||||
case "", "1", "true", "yes", "on":
|
||||
|
|
|
|||
24
internal/vmware/fixture_records_test.go
Normal file
24
internal/vmware/fixture_records_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package vmware
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFixtureRecordsIgnoreFeatureFlag(t *testing.T) {
|
||||
previous := IsFeatureEnabled()
|
||||
SetFeatureEnabled(false)
|
||||
t.Cleanup(func() { SetFeatureEnabled(previous) })
|
||||
|
||||
snapshot := DefaultFixtures()
|
||||
records := FixtureRecords(snapshot)
|
||||
if len(records) == 0 {
|
||||
t.Fatal("expected fixture records even when feature flag is disabled")
|
||||
}
|
||||
|
||||
hostID := vmwareSourceID(snapshot.ConnectionID, "host", snapshot.Hosts[0].Host)
|
||||
for _, record := range records {
|
||||
if record.SourceID == hostID {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("expected fixture records to include host source %q", hostID)
|
||||
}
|
||||
|
|
@ -311,20 +311,33 @@ func (p *Provider) Snapshot() *InventorySnapshot {
|
|||
return snapshot
|
||||
}
|
||||
|
||||
// FixtureRecords projects a VMware fixture snapshot into canonical unified
|
||||
// resource ingest records without consulting the runtime feature flag.
|
||||
func FixtureRecords(snapshot InventorySnapshot) []unifiedresources.IngestRecord {
|
||||
return vmwareRecordsFromSnapshot(&snapshot, nil)
|
||||
}
|
||||
|
||||
// Records returns canonical VMware unified resources if the integration is enabled.
|
||||
func (p *Provider) Records() []unifiedresources.IngestRecord {
|
||||
if p == nil || !IsFeatureEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshot := p.Snapshot()
|
||||
return vmwareRecordsFromSnapshot(p.Snapshot(), p.now)
|
||||
}
|
||||
|
||||
func vmwareRecordsFromSnapshot(snapshot *InventorySnapshot, now func() time.Time) []unifiedresources.IngestRecord {
|
||||
if snapshot == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
collectedAt := snapshot.CollectedAt
|
||||
if collectedAt.IsZero() {
|
||||
collectedAt = p.now()
|
||||
if now != nil {
|
||||
collectedAt = now().UTC()
|
||||
} else {
|
||||
collectedAt = time.Now().UTC()
|
||||
}
|
||||
}
|
||||
|
||||
connectionName := firstNonEmptyTrimmed(snapshot.ConnectionName, snapshot.VCenterHost, snapshot.ConnectionID)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue