mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 08:57:12 +00:00
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
|
|
proxmoxmapper "github.com/rcourtman/pulse-go-rewrite/internal/recovery/mapper/proxmox"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
func (m *Monitor) ingestRecoveryPointsAsync(points []recovery.RecoveryPoint) {
|
|
if m == nil || len(points) == 0 {
|
|
return
|
|
}
|
|
go func() {
|
|
// SQLite upserts are usually quick, but allow a bit of time for large batches.
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
m.ingestRecoveryPointsBestEffort(ctx, points)
|
|
}()
|
|
}
|
|
|
|
func (m *Monitor) ingestRecoveryPointsBestEffort(ctx context.Context, points []recovery.RecoveryPoint) {
|
|
if m == nil || len(points) == 0 {
|
|
return
|
|
}
|
|
|
|
m.mu.RLock()
|
|
rm := m.recoveryManager
|
|
orgID := strings.TrimSpace(m.orgID)
|
|
m.mu.RUnlock()
|
|
|
|
if rm == nil {
|
|
return
|
|
}
|
|
if orgID == "" {
|
|
orgID = "default"
|
|
}
|
|
|
|
store, err := rm.StoreForOrg(orgID)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("org_id", orgID).Msg("Failed to open recovery store for backup ingestion")
|
|
return
|
|
}
|
|
|
|
if err := store.UpsertPoints(ctx, points); err != nil {
|
|
log.Warn().Err(err).Str("org_id", orgID).Int("points", len(points)).Msg("Failed to upsert recovery points from backup polling")
|
|
}
|
|
}
|
|
|
|
func (m *Monitor) purgeStalePVEPBSBackupsBestEffort(ctx context.Context) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
|
|
m.mu.RLock()
|
|
rm := m.recoveryManager
|
|
orgID := strings.TrimSpace(m.orgID)
|
|
hasPBSDirectConnection := m.config != nil && len(m.config.PBSInstances) > 0
|
|
m.mu.RUnlock()
|
|
|
|
if !hasPBSDirectConnection || rm == nil {
|
|
return
|
|
}
|
|
if orgID == "" {
|
|
orgID = "default"
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
purgeCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
defer cancel()
|
|
|
|
store, err := rm.StoreForOrg(orgID)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("org_id", orgID).Msg("Failed to open recovery store for stale PBS backup purge")
|
|
return
|
|
}
|
|
|
|
if err := store.PurgeStalePVEPBSBackups(purgeCtx); err != nil {
|
|
log.Warn().Err(err).Str("org_id", orgID).Msg("Failed to purge stale PVE-sourced PBS backup entries")
|
|
}
|
|
}
|
|
|
|
func buildProxmoxGuestInfoIndex(readState unifiedresources.ReadState) map[string]proxmoxmapper.GuestInfo {
|
|
if readState == nil {
|
|
return map[string]proxmoxmapper.GuestInfo{}
|
|
}
|
|
vmViews := readState.VMs()
|
|
containerViews := readState.Containers()
|
|
out := make(map[string]proxmoxmapper.GuestInfo, len(vmViews)+len(containerViews))
|
|
|
|
for _, vm := range vmViews {
|
|
if vm == nil || vm.Template() {
|
|
continue
|
|
}
|
|
key := proxmoxmapperKey(vm.Instance(), vm.Node(), vm.VMID())
|
|
sourceID := strings.TrimSpace(vm.ID())
|
|
if sourceID == "" {
|
|
sourceID = makeGuestID(vm.Instance(), vm.Node(), vm.VMID())
|
|
}
|
|
out[key] = proxmoxmapper.GuestInfo{
|
|
SourceID: sourceID,
|
|
ResourceType: unifiedresources.ResourceTypeVM,
|
|
Name: strings.TrimSpace(vm.Name()),
|
|
}
|
|
}
|
|
|
|
for _, ct := range containerViews {
|
|
if ct == nil || ct.Template() {
|
|
continue
|
|
}
|
|
key := proxmoxmapperKey(ct.Instance(), ct.Node(), ct.VMID())
|
|
sourceID := strings.TrimSpace(ct.ID())
|
|
if sourceID == "" {
|
|
sourceID = makeGuestID(ct.Instance(), ct.Node(), ct.VMID())
|
|
}
|
|
out[key] = proxmoxmapper.GuestInfo{
|
|
SourceID: sourceID,
|
|
ResourceType: unifiedresources.ResourceTypeSystemContainer,
|
|
Name: strings.TrimSpace(ct.Name()),
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func buildPBSGuestCandidates(readState unifiedresources.ReadState) map[string][]proxmoxmapper.GuestCandidate {
|
|
if readState == nil {
|
|
return map[string][]proxmoxmapper.GuestCandidate{}
|
|
}
|
|
vmViews := readState.VMs()
|
|
containerViews := readState.Containers()
|
|
out := make(map[string][]proxmoxmapper.GuestCandidate, len(vmViews)+len(containerViews))
|
|
|
|
for _, vm := range vmViews {
|
|
if vm == nil || vm.Template() || vm.VMID() <= 0 {
|
|
continue
|
|
}
|
|
key := "vm:" + fmt.Sprintf("%d", vm.VMID())
|
|
sourceID := strings.TrimSpace(vm.ID())
|
|
if sourceID == "" {
|
|
sourceID = makeGuestID(vm.Instance(), vm.Node(), vm.VMID())
|
|
}
|
|
out[key] = append(out[key], proxmoxmapper.GuestCandidate{
|
|
SourceID: sourceID,
|
|
ResourceType: unifiedresources.ResourceTypeVM,
|
|
DisplayName: strings.TrimSpace(vm.Name()),
|
|
InstanceName: strings.TrimSpace(vm.Instance()),
|
|
NodeName: strings.TrimSpace(vm.Node()),
|
|
VMID: vm.VMID(),
|
|
})
|
|
}
|
|
|
|
for _, ct := range containerViews {
|
|
if ct == nil || ct.Template() || ct.VMID() <= 0 {
|
|
continue
|
|
}
|
|
key := "ct:" + fmt.Sprintf("%d", ct.VMID())
|
|
sourceID := strings.TrimSpace(ct.ID())
|
|
if sourceID == "" {
|
|
sourceID = makeGuestID(ct.Instance(), ct.Node(), ct.VMID())
|
|
}
|
|
out[key] = append(out[key], proxmoxmapper.GuestCandidate{
|
|
SourceID: sourceID,
|
|
ResourceType: unifiedresources.ResourceTypeSystemContainer,
|
|
DisplayName: strings.TrimSpace(ct.Name()),
|
|
InstanceName: strings.TrimSpace(ct.Instance()),
|
|
NodeName: strings.TrimSpace(ct.Node()),
|
|
VMID: ct.VMID(),
|
|
})
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func proxmoxmapperKey(instanceName, nodeName string, vmid int) string {
|
|
// Keep key format in one place so ingestion and mapping stay consistent.
|
|
return fmt.Sprintf("%s|%s|%d", strings.TrimSpace(instanceName), strings.TrimSpace(nodeName), vmid)
|
|
}
|