mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-23 03:33:53 +00:00
c73e78aeea
Contract-Neutral: RC qualification fixes preserve existing public API, tenant, monitoring, and organization contracts while correcting canonical runtime ownership and test fixtures.
251 lines
6.5 KiB
Go
251 lines
6.5 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
func TestMultiTenantMonitorRemoveTenant(t *testing.T) {
|
|
monitor := &Monitor{}
|
|
mtm := &MultiTenantMonitor{
|
|
monitors: map[string]*Monitor{
|
|
"org-1": monitor,
|
|
},
|
|
}
|
|
|
|
mtm.RemoveTenant("org-1")
|
|
if _, ok := mtm.monitors["org-1"]; ok {
|
|
t.Fatalf("expected org-1 to be removed")
|
|
}
|
|
|
|
// Ensure removal of missing orgs is a no-op.
|
|
mtm.RemoveTenant("missing")
|
|
}
|
|
|
|
func TestMultiTenantMonitorRemoveTenantCancelsTenantRuntime(t *testing.T) {
|
|
mtp, _ := newTestTenantPersistence(t)
|
|
baseCfg := &config.Config{DataPath: t.TempDir()}
|
|
mtm := NewMultiTenantMonitor(baseCfg, mtp, nil)
|
|
t.Cleanup(mtm.Stop)
|
|
|
|
if err := mtp.SaveOrganization(&models.Organization{
|
|
ID: "org-stop",
|
|
DisplayName: "Org Stop",
|
|
}); err != nil {
|
|
t.Fatalf("SaveOrganization(org-stop) error = %v", err)
|
|
}
|
|
|
|
monitor, err := mtm.GetMonitor("org-stop")
|
|
if err != nil {
|
|
t.Fatalf("GetMonitor(org-stop) error = %v", err)
|
|
}
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for monitor.getRuntimeContext() == nil && time.Now().Before(deadline) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
runtimeCtx := monitor.getRuntimeContext()
|
|
if runtimeCtx == nil {
|
|
t.Fatal("expected tenant monitor runtime context to be initialized")
|
|
}
|
|
|
|
runtimeDone := runtimeCtx.Done()
|
|
select {
|
|
case <-runtimeDone:
|
|
t.Fatal("expected tenant runtime context to be active before removal")
|
|
default:
|
|
}
|
|
|
|
mtm.RemoveTenant("org-stop")
|
|
|
|
select {
|
|
case <-runtimeDone:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("expected RemoveTenant to cancel the tenant runtime context")
|
|
}
|
|
|
|
if _, ok := mtm.monitors["org-stop"]; ok {
|
|
t.Fatal("expected org-stop to be removed from monitor cache")
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorTenantDeletionBlocksLazyRecreation(t *testing.T) {
|
|
mtp, _ := newTestTenantPersistence(t)
|
|
baseCfg := &config.Config{DataPath: t.TempDir()}
|
|
mtm := NewMultiTenantMonitor(baseCfg, mtp, nil)
|
|
t.Cleanup(mtm.Stop)
|
|
|
|
if err := mtp.SaveOrganization(&models.Organization{
|
|
ID: "org-delete",
|
|
DisplayName: "Org Delete",
|
|
}); err != nil {
|
|
t.Fatalf("SaveOrganization(org-delete) error = %v", err)
|
|
}
|
|
|
|
first, err := mtm.GetMonitor("org-delete")
|
|
if err != nil {
|
|
t.Fatalf("GetMonitor(org-delete) error = %v", err)
|
|
}
|
|
|
|
mtm.BeginTenantDeletion("org-delete")
|
|
if _, err := mtm.GetMonitor("org-delete"); err == nil {
|
|
t.Fatal("expected tenant deletion guard to block lazy monitor recreation")
|
|
}
|
|
|
|
mtm.FinishTenantDeletion("org-delete")
|
|
second, err := mtm.GetMonitor("org-delete")
|
|
if err != nil {
|
|
t.Fatalf("GetMonitor(org-delete) after deletion guard error = %v", err)
|
|
}
|
|
if second == first {
|
|
t.Fatal("expected a fresh monitor after the deletion guard is released")
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorGetMonitor_MetricsIsolationByTenant(t *testing.T) {
|
|
orgAMonitor := &Monitor{
|
|
metricsHistory: NewMetricsHistory(32, time.Hour),
|
|
}
|
|
orgAMonitor.SetOrgID("org-a")
|
|
|
|
orgBMonitor := &Monitor{
|
|
metricsHistory: NewMetricsHistory(32, time.Hour),
|
|
}
|
|
orgBMonitor.SetOrgID("org-b")
|
|
|
|
mtm := &MultiTenantMonitor{
|
|
monitors: map[string]*Monitor{
|
|
"org-a": orgAMonitor,
|
|
"org-b": orgBMonitor,
|
|
},
|
|
}
|
|
|
|
gotA, err := mtm.GetMonitor("org-a")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting org-a monitor: %v", err)
|
|
}
|
|
gotB, err := mtm.GetMonitor("org-b")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting org-b monitor: %v", err)
|
|
}
|
|
|
|
if gotA == gotB {
|
|
t.Fatalf("expected distinct monitor instances per tenant")
|
|
}
|
|
if gotA.GetOrgID() != "org-a" {
|
|
t.Fatalf("expected org-a monitor org id, got %q", gotA.GetOrgID())
|
|
}
|
|
if gotB.GetOrgID() != "org-b" {
|
|
t.Fatalf("expected org-b monitor org id, got %q", gotB.GetOrgID())
|
|
}
|
|
|
|
now := time.Now()
|
|
gotA.metricsHistory.AddGuestMetric("vm-1", "cpu", 73.5, now)
|
|
|
|
aMetrics := gotA.GetGuestMetrics("vm-1", time.Hour)
|
|
bMetrics := gotB.GetGuestMetrics("vm-1", time.Hour)
|
|
if len(aMetrics["cpu"]) != 1 {
|
|
t.Fatalf("expected org-a cpu series length 1, got %d", len(aMetrics["cpu"]))
|
|
}
|
|
if len(bMetrics["cpu"]) != 0 {
|
|
t.Fatalf("expected org-b cpu series to remain empty, got %d points", len(bMetrics["cpu"]))
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorGetMonitorRejectsEmptyOrgID(t *testing.T) {
|
|
mtm := &MultiTenantMonitor{}
|
|
if _, err := mtm.GetMonitor(" "); err == nil {
|
|
t.Fatal("expected error for empty org ID")
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorGetMonitorRejectsUnknownTenant(t *testing.T) {
|
|
baseDir := t.TempDir()
|
|
mtp := config.NewMultiTenantPersistence(baseDir)
|
|
baseCfg := &config.Config{DataPath: baseDir}
|
|
mtm := NewMultiTenantMonitor(baseCfg, mtp, nil)
|
|
t.Cleanup(mtm.Stop)
|
|
|
|
if _, err := mtm.GetMonitor("org-missing"); err == nil {
|
|
t.Fatal("expected error for unknown org")
|
|
}
|
|
|
|
orgDir := filepath.Join(baseDir, "orgs", "org-missing")
|
|
if _, err := os.Stat(orgDir); !os.IsNotExist(err) {
|
|
t.Fatalf("expected unknown org monitor lookup to avoid creating %s, stat err = %v", orgDir, err)
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorPeekMonitorTrimsOrgID(t *testing.T) {
|
|
expected := &Monitor{}
|
|
mtm := &MultiTenantMonitor{
|
|
monitors: map[string]*Monitor{
|
|
"org-a": expected,
|
|
},
|
|
}
|
|
|
|
got, ok := mtm.PeekMonitor(" org-a ")
|
|
if !ok {
|
|
t.Fatal("expected trimmed org ID lookup to succeed")
|
|
}
|
|
if got != expected {
|
|
t.Fatalf("expected same monitor pointer, got %p want %p", got, expected)
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorSetMonitorInitializerAppliesToExisting(t *testing.T) {
|
|
first := &Monitor{}
|
|
second := &Monitor{}
|
|
mtm := &MultiTenantMonitor{
|
|
monitors: map[string]*Monitor{
|
|
"org-1": first,
|
|
"org-2": second,
|
|
},
|
|
}
|
|
|
|
var called atomic.Int32
|
|
mtm.SetMonitorInitializer(func(m *Monitor) {
|
|
if m != nil {
|
|
called.Add(1)
|
|
}
|
|
})
|
|
|
|
if called.Load() != 2 {
|
|
t.Fatalf("expected initializer to run for existing monitors, got %d", called.Load())
|
|
}
|
|
}
|
|
|
|
func TestMultiTenantMonitorSetMonitorInitializerAppliesToNewMonitor(t *testing.T) {
|
|
mtp, _ := newTestTenantPersistence(t)
|
|
baseCfg := &config.Config{DataPath: t.TempDir()}
|
|
mtm := NewMultiTenantMonitor(baseCfg, mtp, nil)
|
|
t.Cleanup(mtm.Stop)
|
|
|
|
var called atomic.Int32
|
|
mtm.SetMonitorInitializer(func(m *Monitor) {
|
|
if m != nil {
|
|
called.Add(1)
|
|
}
|
|
})
|
|
|
|
if err := mtp.SaveOrganization(&models.Organization{
|
|
ID: "org-init",
|
|
DisplayName: "Org Init",
|
|
}); err != nil {
|
|
t.Fatalf("SaveOrganization(org-init) error = %v", err)
|
|
}
|
|
|
|
if _, err := mtm.GetMonitor("org-init"); err != nil {
|
|
t.Fatalf("GetMonitor(org-init) error = %v", err)
|
|
}
|
|
if called.Load() != 1 {
|
|
t.Fatalf("expected initializer to run for new monitor, got %d", called.Load())
|
|
}
|
|
}
|