mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-15 01:07:32 +00:00
refactor: Extract filter evaluation functions to separate file
Move guest threshold resolution and filter evaluation logic from alerts.go to filter_evaluation.go for better code organization. Extracted functions: - evaluateFilterCondition - evaluateVMCondition - evaluateContainerCondition - evaluateFilterStack - getGuestThresholds This reduces alerts.go by ~370 lines and isolates the rule evaluation engine that determines which thresholds apply to guests.
This commit is contained in:
parent
72420a616a
commit
f02a340d72
2 changed files with 380 additions and 371 deletions
|
|
@ -7799,377 +7799,6 @@ func (m *Manager) guestHasMonitorOnlyAlerts(guestID string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// evaluateFilterCondition evaluates a single filter condition against a guest
|
||||
func (m *Manager) evaluateFilterCondition(guest interface{}, condition FilterCondition) bool {
|
||||
switch g := guest.(type) {
|
||||
case models.VM:
|
||||
return m.evaluateVMCondition(g, condition)
|
||||
case models.Container:
|
||||
return m.evaluateContainerCondition(g, condition)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateVMCondition evaluates a filter condition against a VM
|
||||
func (m *Manager) evaluateVMCondition(vm models.VM, condition FilterCondition) bool {
|
||||
switch condition.Type {
|
||||
case "metric":
|
||||
value := 0.0
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "cpu":
|
||||
value = vm.CPU * 100
|
||||
case "memory":
|
||||
value = vm.Memory.Usage
|
||||
case "disk":
|
||||
value = vm.Disk.Usage
|
||||
case "diskread":
|
||||
value = float64(vm.DiskRead) / 1024 / 1024 // Convert to MB/s
|
||||
case "diskwrite":
|
||||
value = float64(vm.DiskWrite) / 1024 / 1024
|
||||
case "networkin":
|
||||
value = float64(vm.NetworkIn) / 1024 / 1024
|
||||
case "networkout":
|
||||
value = float64(vm.NetworkOut) / 1024 / 1024
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
condValue, ok := condition.Value.(float64)
|
||||
if !ok {
|
||||
// Try to convert from int
|
||||
if intVal, ok := condition.Value.(int); ok {
|
||||
condValue = float64(intVal)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch condition.Operator {
|
||||
case ">":
|
||||
return value > condValue
|
||||
case "<":
|
||||
return value < condValue
|
||||
case ">=":
|
||||
return value >= condValue
|
||||
case "<=":
|
||||
return value <= condValue
|
||||
case "=", "==":
|
||||
return value >= condValue-0.5 && value <= condValue+0.5
|
||||
}
|
||||
|
||||
case "text":
|
||||
searchValue := strings.ToLower(fmt.Sprintf("%v", condition.Value))
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "name":
|
||||
return strings.Contains(strings.ToLower(vm.Name), searchValue)
|
||||
case "node":
|
||||
return strings.Contains(strings.ToLower(vm.Node), searchValue)
|
||||
case "vmid":
|
||||
return strings.Contains(vm.ID, searchValue)
|
||||
}
|
||||
|
||||
case "raw":
|
||||
if condition.RawText != "" {
|
||||
term := strings.ToLower(condition.RawText)
|
||||
return strings.Contains(strings.ToLower(vm.Name), term) ||
|
||||
strings.Contains(vm.ID, term) ||
|
||||
strings.Contains(strings.ToLower(vm.Node), term) ||
|
||||
strings.Contains(strings.ToLower(vm.Status), term)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// evaluateContainerCondition evaluates a filter condition against a Container
|
||||
func (m *Manager) evaluateContainerCondition(ct models.Container, condition FilterCondition) bool {
|
||||
// Similar logic to evaluateVMCondition but for Container type
|
||||
switch condition.Type {
|
||||
case "metric":
|
||||
value := 0.0
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "cpu":
|
||||
value = ct.CPU * 100
|
||||
case "memory":
|
||||
value = ct.Memory.Usage
|
||||
case "disk":
|
||||
value = ct.Disk.Usage
|
||||
case "diskread":
|
||||
value = float64(ct.DiskRead) / 1024 / 1024
|
||||
case "diskwrite":
|
||||
value = float64(ct.DiskWrite) / 1024 / 1024
|
||||
case "networkin":
|
||||
value = float64(ct.NetworkIn) / 1024 / 1024
|
||||
case "networkout":
|
||||
value = float64(ct.NetworkOut) / 1024 / 1024
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
condValue, ok := condition.Value.(float64)
|
||||
if !ok {
|
||||
if intVal, ok := condition.Value.(int); ok {
|
||||
condValue = float64(intVal)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch condition.Operator {
|
||||
case ">":
|
||||
return value > condValue
|
||||
case "<":
|
||||
return value < condValue
|
||||
case ">=":
|
||||
return value >= condValue
|
||||
case "<=":
|
||||
return value <= condValue
|
||||
case "=", "==":
|
||||
return value >= condValue-0.5 && value <= condValue+0.5
|
||||
}
|
||||
|
||||
case "text":
|
||||
searchValue := strings.ToLower(fmt.Sprintf("%v", condition.Value))
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "name":
|
||||
return strings.Contains(strings.ToLower(ct.Name), searchValue)
|
||||
case "node":
|
||||
return strings.Contains(strings.ToLower(ct.Node), searchValue)
|
||||
case "vmid":
|
||||
return strings.Contains(ct.ID, searchValue)
|
||||
}
|
||||
|
||||
case "raw":
|
||||
if condition.RawText != "" {
|
||||
term := strings.ToLower(condition.RawText)
|
||||
return strings.Contains(strings.ToLower(ct.Name), term) ||
|
||||
strings.Contains(ct.ID, term) ||
|
||||
strings.Contains(strings.ToLower(ct.Node), term) ||
|
||||
strings.Contains(strings.ToLower(ct.Status), term)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// evaluateFilterStack evaluates a filter stack against a guest
|
||||
func (m *Manager) evaluateFilterStack(guest interface{}, stack FilterStack) bool {
|
||||
if len(stack.Filters) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
results := make([]bool, len(stack.Filters))
|
||||
for i, filter := range stack.Filters {
|
||||
results[i] = m.evaluateFilterCondition(guest, filter)
|
||||
}
|
||||
|
||||
// Apply logical operator
|
||||
if stack.LogicalOperator == "AND" {
|
||||
for _, result := range results {
|
||||
if !result {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// OR
|
||||
for _, result := range results {
|
||||
if result {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getGuestThresholds returns the appropriate thresholds for a guest
|
||||
// Priority: Guest-specific overrides > Custom rules (by priority) > Global defaults
|
||||
func (m *Manager) getGuestThresholds(guest interface{}, guestID string) ThresholdConfig {
|
||||
// Start with defaults
|
||||
thresholds := m.config.GuestDefaults
|
||||
|
||||
// Check custom rules (sorted by priority, highest first)
|
||||
var applicableRule *CustomAlertRule
|
||||
highestPriority := -1
|
||||
|
||||
for i := range m.config.CustomRules {
|
||||
rule := &m.config.CustomRules[i]
|
||||
if !rule.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this rule applies to the guest
|
||||
if m.evaluateFilterStack(guest, rule.FilterConditions) {
|
||||
if rule.Priority > highestPriority {
|
||||
applicableRule = rule
|
||||
highestPriority = rule.Priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply custom rule thresholds if found
|
||||
if applicableRule != nil {
|
||||
if applicableRule.Thresholds.CPU != nil {
|
||||
thresholds.CPU = ensureHysteresisThreshold(applicableRule.Thresholds.CPU)
|
||||
} else if applicableRule.Thresholds.CPULegacy != nil {
|
||||
thresholds.CPU = m.convertLegacyThreshold(applicableRule.Thresholds.CPULegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.Memory != nil {
|
||||
thresholds.Memory = ensureHysteresisThreshold(applicableRule.Thresholds.Memory)
|
||||
} else if applicableRule.Thresholds.MemoryLegacy != nil {
|
||||
thresholds.Memory = m.convertLegacyThreshold(applicableRule.Thresholds.MemoryLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.Disk != nil {
|
||||
thresholds.Disk = ensureHysteresisThreshold(applicableRule.Thresholds.Disk)
|
||||
} else if applicableRule.Thresholds.DiskLegacy != nil {
|
||||
thresholds.Disk = m.convertLegacyThreshold(applicableRule.Thresholds.DiskLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DiskRead != nil {
|
||||
thresholds.DiskRead = ensureHysteresisThreshold(applicableRule.Thresholds.DiskRead)
|
||||
} else if applicableRule.Thresholds.DiskReadLegacy != nil {
|
||||
thresholds.DiskRead = m.convertLegacyThreshold(applicableRule.Thresholds.DiskReadLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DiskWrite != nil {
|
||||
thresholds.DiskWrite = ensureHysteresisThreshold(applicableRule.Thresholds.DiskWrite)
|
||||
} else if applicableRule.Thresholds.DiskWriteLegacy != nil {
|
||||
thresholds.DiskWrite = m.convertLegacyThreshold(applicableRule.Thresholds.DiskWriteLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.NetworkIn != nil {
|
||||
thresholds.NetworkIn = ensureHysteresisThreshold(applicableRule.Thresholds.NetworkIn)
|
||||
} else if applicableRule.Thresholds.NetworkInLegacy != nil {
|
||||
thresholds.NetworkIn = m.convertLegacyThreshold(applicableRule.Thresholds.NetworkInLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.NetworkOut != nil {
|
||||
thresholds.NetworkOut = ensureHysteresisThreshold(applicableRule.Thresholds.NetworkOut)
|
||||
} else if applicableRule.Thresholds.NetworkOutLegacy != nil {
|
||||
thresholds.NetworkOut = m.convertLegacyThreshold(applicableRule.Thresholds.NetworkOutLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DisableConnectivity {
|
||||
thresholds.DisableConnectivity = true
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("guest", guestID).
|
||||
Str("rule", applicableRule.Name).
|
||||
Int("priority", applicableRule.Priority).
|
||||
Msg("Applied custom alert rule")
|
||||
}
|
||||
|
||||
// Finally check guest-specific overrides (highest priority)
|
||||
// First try the new stable ID format (instance-VMID)
|
||||
override, exists := m.config.Overrides[guestID]
|
||||
|
||||
// If not found, try legacy ID formats for migration
|
||||
if !exists {
|
||||
var legacyID string
|
||||
var node string
|
||||
var vmid int
|
||||
var instance string
|
||||
|
||||
// Extract node, vmid, and instance from the guest object
|
||||
switch g := guest.(type) {
|
||||
case models.VM:
|
||||
node = g.Node
|
||||
vmid = g.VMID
|
||||
instance = g.Instance
|
||||
case models.Container:
|
||||
node = g.Node
|
||||
vmid = g.VMID
|
||||
instance = g.Instance
|
||||
default:
|
||||
// Not a VM or container, skip legacy migration
|
||||
goto skipLegacyMigration
|
||||
}
|
||||
|
||||
// Try legacy format: instance-node-VMID
|
||||
if instance != node {
|
||||
legacyID = fmt.Sprintf("%s-%s-%d", instance, node, vmid)
|
||||
if legacyOverride, legacyExists := m.config.Overrides[legacyID]; legacyExists {
|
||||
log.Info().
|
||||
Str("legacyID", legacyID).
|
||||
Str("newID", guestID).
|
||||
Msg("Migrating guest override from legacy ID format")
|
||||
|
||||
// Move to new ID
|
||||
m.config.Overrides[guestID] = legacyOverride
|
||||
delete(m.config.Overrides, legacyID)
|
||||
|
||||
// Config will be persisted on next save cycle
|
||||
override = legacyOverride
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, try standalone format: node-VMID
|
||||
if !exists && instance == node {
|
||||
legacyID = fmt.Sprintf("%s-%d", node, vmid)
|
||||
if legacyOverride, legacyExists := m.config.Overrides[legacyID]; legacyExists {
|
||||
log.Info().
|
||||
Str("legacyID", legacyID).
|
||||
Str("newID", guestID).
|
||||
Msg("Migrating guest override from legacy standalone ID format")
|
||||
|
||||
// Move to new ID
|
||||
m.config.Overrides[guestID] = legacyOverride
|
||||
delete(m.config.Overrides, legacyID)
|
||||
|
||||
// Config will be persisted on next save cycle
|
||||
override = legacyOverride
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipLegacyMigration:
|
||||
if exists {
|
||||
// Apply the disabled flag if set
|
||||
if override.Disabled {
|
||||
thresholds.Disabled = true
|
||||
}
|
||||
if override.DisableConnectivity {
|
||||
thresholds.DisableConnectivity = true
|
||||
}
|
||||
|
||||
if override.CPU != nil {
|
||||
thresholds.CPU = ensureHysteresisThreshold(override.CPU)
|
||||
} else if override.CPULegacy != nil {
|
||||
thresholds.CPU = m.convertLegacyThreshold(override.CPULegacy)
|
||||
}
|
||||
if override.Memory != nil {
|
||||
thresholds.Memory = ensureHysteresisThreshold(override.Memory)
|
||||
} else if override.MemoryLegacy != nil {
|
||||
thresholds.Memory = m.convertLegacyThreshold(override.MemoryLegacy)
|
||||
}
|
||||
if override.Disk != nil {
|
||||
thresholds.Disk = ensureHysteresisThreshold(override.Disk)
|
||||
} else if override.DiskLegacy != nil {
|
||||
thresholds.Disk = m.convertLegacyThreshold(override.DiskLegacy)
|
||||
}
|
||||
if override.DiskRead != nil {
|
||||
thresholds.DiskRead = ensureHysteresisThreshold(override.DiskRead)
|
||||
} else if override.DiskReadLegacy != nil {
|
||||
thresholds.DiskRead = m.convertLegacyThreshold(override.DiskReadLegacy)
|
||||
}
|
||||
if override.DiskWrite != nil {
|
||||
thresholds.DiskWrite = ensureHysteresisThreshold(override.DiskWrite)
|
||||
} else if override.DiskWriteLegacy != nil {
|
||||
thresholds.DiskWrite = m.convertLegacyThreshold(override.DiskWriteLegacy)
|
||||
}
|
||||
if override.NetworkIn != nil {
|
||||
thresholds.NetworkIn = ensureHysteresisThreshold(override.NetworkIn)
|
||||
} else if override.NetworkInLegacy != nil {
|
||||
thresholds.NetworkIn = m.convertLegacyThreshold(override.NetworkInLegacy)
|
||||
}
|
||||
if override.NetworkOut != nil {
|
||||
thresholds.NetworkOut = ensureHysteresisThreshold(override.NetworkOut)
|
||||
} else if override.NetworkOutLegacy != nil {
|
||||
thresholds.NetworkOut = m.convertLegacyThreshold(override.NetworkOutLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
return thresholds
|
||||
}
|
||||
|
||||
// checkRateLimit checks if an alert has exceeded rate limit
|
||||
func (m *Manager) checkRateLimit(alertID string) bool {
|
||||
if m.config.Schedule.MaxAlertsHour <= 0 {
|
||||
|
|
|
|||
380
internal/alerts/filter_evaluation.go
Normal file
380
internal/alerts/filter_evaluation.go
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
package alerts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// evaluateFilterCondition evaluates a single filter condition against a guest
|
||||
func (m *Manager) evaluateFilterCondition(guest interface{}, condition FilterCondition) bool {
|
||||
switch g := guest.(type) {
|
||||
case models.VM:
|
||||
return m.evaluateVMCondition(g, condition)
|
||||
case models.Container:
|
||||
return m.evaluateContainerCondition(g, condition)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// evaluateVMCondition evaluates a filter condition against a VM
|
||||
func (m *Manager) evaluateVMCondition(vm models.VM, condition FilterCondition) bool {
|
||||
switch condition.Type {
|
||||
case "metric":
|
||||
value := 0.0
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "cpu":
|
||||
value = vm.CPU * 100
|
||||
case "memory":
|
||||
value = vm.Memory.Usage
|
||||
case "disk":
|
||||
value = vm.Disk.Usage
|
||||
case "diskread":
|
||||
value = float64(vm.DiskRead) / 1024 / 1024 // Convert to MB/s
|
||||
case "diskwrite":
|
||||
value = float64(vm.DiskWrite) / 1024 / 1024
|
||||
case "networkin":
|
||||
value = float64(vm.NetworkIn) / 1024 / 1024
|
||||
case "networkout":
|
||||
value = float64(vm.NetworkOut) / 1024 / 1024
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
condValue, ok := condition.Value.(float64)
|
||||
if !ok {
|
||||
// Try to convert from int
|
||||
if intVal, ok := condition.Value.(int); ok {
|
||||
condValue = float64(intVal)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch condition.Operator {
|
||||
case ">":
|
||||
return value > condValue
|
||||
case "<":
|
||||
return value < condValue
|
||||
case ">=":
|
||||
return value >= condValue
|
||||
case "<=":
|
||||
return value <= condValue
|
||||
case "=", "==":
|
||||
return value >= condValue-0.5 && value <= condValue+0.5
|
||||
}
|
||||
|
||||
case "text":
|
||||
searchValue := strings.ToLower(fmt.Sprintf("%v", condition.Value))
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "name":
|
||||
return strings.Contains(strings.ToLower(vm.Name), searchValue)
|
||||
case "node":
|
||||
return strings.Contains(strings.ToLower(vm.Node), searchValue)
|
||||
case "vmid":
|
||||
return strings.Contains(vm.ID, searchValue)
|
||||
}
|
||||
|
||||
case "raw":
|
||||
if condition.RawText != "" {
|
||||
term := strings.ToLower(condition.RawText)
|
||||
return strings.Contains(strings.ToLower(vm.Name), term) ||
|
||||
strings.Contains(vm.ID, term) ||
|
||||
strings.Contains(strings.ToLower(vm.Node), term) ||
|
||||
strings.Contains(strings.ToLower(vm.Status), term)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// evaluateContainerCondition evaluates a filter condition against a Container
|
||||
func (m *Manager) evaluateContainerCondition(ct models.Container, condition FilterCondition) bool {
|
||||
// Similar logic to evaluateVMCondition but for Container type
|
||||
switch condition.Type {
|
||||
case "metric":
|
||||
value := 0.0
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "cpu":
|
||||
value = ct.CPU * 100
|
||||
case "memory":
|
||||
value = ct.Memory.Usage
|
||||
case "disk":
|
||||
value = ct.Disk.Usage
|
||||
case "diskread":
|
||||
value = float64(ct.DiskRead) / 1024 / 1024
|
||||
case "diskwrite":
|
||||
value = float64(ct.DiskWrite) / 1024 / 1024
|
||||
case "networkin":
|
||||
value = float64(ct.NetworkIn) / 1024 / 1024
|
||||
case "networkout":
|
||||
value = float64(ct.NetworkOut) / 1024 / 1024
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
condValue, ok := condition.Value.(float64)
|
||||
if !ok {
|
||||
if intVal, ok := condition.Value.(int); ok {
|
||||
condValue = float64(intVal)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch condition.Operator {
|
||||
case ">":
|
||||
return value > condValue
|
||||
case "<":
|
||||
return value < condValue
|
||||
case ">=":
|
||||
return value >= condValue
|
||||
case "<=":
|
||||
return value <= condValue
|
||||
case "=", "==":
|
||||
return value >= condValue-0.5 && value <= condValue+0.5
|
||||
}
|
||||
|
||||
case "text":
|
||||
searchValue := strings.ToLower(fmt.Sprintf("%v", condition.Value))
|
||||
switch strings.ToLower(condition.Field) {
|
||||
case "name":
|
||||
return strings.Contains(strings.ToLower(ct.Name), searchValue)
|
||||
case "node":
|
||||
return strings.Contains(strings.ToLower(ct.Node), searchValue)
|
||||
case "vmid":
|
||||
return strings.Contains(ct.ID, searchValue)
|
||||
}
|
||||
|
||||
case "raw":
|
||||
if condition.RawText != "" {
|
||||
term := strings.ToLower(condition.RawText)
|
||||
return strings.Contains(strings.ToLower(ct.Name), term) ||
|
||||
strings.Contains(ct.ID, term) ||
|
||||
strings.Contains(strings.ToLower(ct.Node), term) ||
|
||||
strings.Contains(strings.ToLower(ct.Status), term)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// evaluateFilterStack evaluates a filter stack against a guest
|
||||
func (m *Manager) evaluateFilterStack(guest interface{}, stack FilterStack) bool {
|
||||
if len(stack.Filters) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
results := make([]bool, len(stack.Filters))
|
||||
for i, filter := range stack.Filters {
|
||||
results[i] = m.evaluateFilterCondition(guest, filter)
|
||||
}
|
||||
|
||||
// Apply logical operator
|
||||
if stack.LogicalOperator == "AND" {
|
||||
for _, result := range results {
|
||||
if !result {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// OR
|
||||
for _, result := range results {
|
||||
if result {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getGuestThresholds returns the appropriate thresholds for a guest
|
||||
// Priority: Guest-specific overrides > Custom rules (by priority) > Global defaults
|
||||
func (m *Manager) getGuestThresholds(guest interface{}, guestID string) ThresholdConfig {
|
||||
// Start with defaults
|
||||
thresholds := m.config.GuestDefaults
|
||||
|
||||
// Check custom rules (sorted by priority, highest first)
|
||||
var applicableRule *CustomAlertRule
|
||||
highestPriority := -1
|
||||
|
||||
for i := range m.config.CustomRules {
|
||||
rule := &m.config.CustomRules[i]
|
||||
if !rule.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this rule applies to the guest
|
||||
if m.evaluateFilterStack(guest, rule.FilterConditions) {
|
||||
if rule.Priority > highestPriority {
|
||||
applicableRule = rule
|
||||
highestPriority = rule.Priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply custom rule thresholds if found
|
||||
if applicableRule != nil {
|
||||
if applicableRule.Thresholds.CPU != nil {
|
||||
thresholds.CPU = ensureHysteresisThreshold(applicableRule.Thresholds.CPU)
|
||||
} else if applicableRule.Thresholds.CPULegacy != nil {
|
||||
thresholds.CPU = m.convertLegacyThreshold(applicableRule.Thresholds.CPULegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.Memory != nil {
|
||||
thresholds.Memory = ensureHysteresisThreshold(applicableRule.Thresholds.Memory)
|
||||
} else if applicableRule.Thresholds.MemoryLegacy != nil {
|
||||
thresholds.Memory = m.convertLegacyThreshold(applicableRule.Thresholds.MemoryLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.Disk != nil {
|
||||
thresholds.Disk = ensureHysteresisThreshold(applicableRule.Thresholds.Disk)
|
||||
} else if applicableRule.Thresholds.DiskLegacy != nil {
|
||||
thresholds.Disk = m.convertLegacyThreshold(applicableRule.Thresholds.DiskLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DiskRead != nil {
|
||||
thresholds.DiskRead = ensureHysteresisThreshold(applicableRule.Thresholds.DiskRead)
|
||||
} else if applicableRule.Thresholds.DiskReadLegacy != nil {
|
||||
thresholds.DiskRead = m.convertLegacyThreshold(applicableRule.Thresholds.DiskReadLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DiskWrite != nil {
|
||||
thresholds.DiskWrite = ensureHysteresisThreshold(applicableRule.Thresholds.DiskWrite)
|
||||
} else if applicableRule.Thresholds.DiskWriteLegacy != nil {
|
||||
thresholds.DiskWrite = m.convertLegacyThreshold(applicableRule.Thresholds.DiskWriteLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.NetworkIn != nil {
|
||||
thresholds.NetworkIn = ensureHysteresisThreshold(applicableRule.Thresholds.NetworkIn)
|
||||
} else if applicableRule.Thresholds.NetworkInLegacy != nil {
|
||||
thresholds.NetworkIn = m.convertLegacyThreshold(applicableRule.Thresholds.NetworkInLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.NetworkOut != nil {
|
||||
thresholds.NetworkOut = ensureHysteresisThreshold(applicableRule.Thresholds.NetworkOut)
|
||||
} else if applicableRule.Thresholds.NetworkOutLegacy != nil {
|
||||
thresholds.NetworkOut = m.convertLegacyThreshold(applicableRule.Thresholds.NetworkOutLegacy)
|
||||
}
|
||||
if applicableRule.Thresholds.DisableConnectivity {
|
||||
thresholds.DisableConnectivity = true
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("guest", guestID).
|
||||
Str("rule", applicableRule.Name).
|
||||
Int("priority", applicableRule.Priority).
|
||||
Msg("Applied custom alert rule")
|
||||
}
|
||||
|
||||
// Finally check guest-specific overrides (highest priority)
|
||||
// First try the new stable ID format (instance-VMID)
|
||||
override, exists := m.config.Overrides[guestID]
|
||||
|
||||
// If not found, try legacy ID formats for migration
|
||||
if !exists {
|
||||
var legacyID string
|
||||
var node string
|
||||
var vmid int
|
||||
var instance string
|
||||
|
||||
// Extract node, vmid, and instance from the guest object
|
||||
switch g := guest.(type) {
|
||||
case models.VM:
|
||||
node = g.Node
|
||||
vmid = g.VMID
|
||||
instance = g.Instance
|
||||
case models.Container:
|
||||
node = g.Node
|
||||
vmid = g.VMID
|
||||
instance = g.Instance
|
||||
default:
|
||||
// Not a VM or container, skip legacy migration
|
||||
goto skipLegacyMigration
|
||||
}
|
||||
|
||||
// Try legacy format: instance-node-VMID
|
||||
if instance != node {
|
||||
legacyID = fmt.Sprintf("%s-%s-%d", instance, node, vmid)
|
||||
if legacyOverride, legacyExists := m.config.Overrides[legacyID]; legacyExists {
|
||||
log.Info().
|
||||
Str("legacyID", legacyID).
|
||||
Str("newID", guestID).
|
||||
Msg("Migrating guest override from legacy ID format")
|
||||
|
||||
// Move to new ID
|
||||
m.config.Overrides[guestID] = legacyOverride
|
||||
delete(m.config.Overrides, legacyID)
|
||||
|
||||
// Config will be persisted on next save cycle
|
||||
override = legacyOverride
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, try standalone format: node-VMID
|
||||
if !exists && instance == node {
|
||||
legacyID = fmt.Sprintf("%s-%d", node, vmid)
|
||||
if legacyOverride, legacyExists := m.config.Overrides[legacyID]; legacyExists {
|
||||
log.Info().
|
||||
Str("legacyID", legacyID).
|
||||
Str("newID", guestID).
|
||||
Msg("Migrating guest override from legacy standalone ID format")
|
||||
|
||||
// Move to new ID
|
||||
m.config.Overrides[guestID] = legacyOverride
|
||||
delete(m.config.Overrides, legacyID)
|
||||
|
||||
// Config will be persisted on next save cycle
|
||||
override = legacyOverride
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipLegacyMigration:
|
||||
if exists {
|
||||
// Apply the disabled flag if set
|
||||
if override.Disabled {
|
||||
thresholds.Disabled = true
|
||||
}
|
||||
if override.DisableConnectivity {
|
||||
thresholds.DisableConnectivity = true
|
||||
}
|
||||
|
||||
if override.CPU != nil {
|
||||
thresholds.CPU = ensureHysteresisThreshold(override.CPU)
|
||||
} else if override.CPULegacy != nil {
|
||||
thresholds.CPU = m.convertLegacyThreshold(override.CPULegacy)
|
||||
}
|
||||
if override.Memory != nil {
|
||||
thresholds.Memory = ensureHysteresisThreshold(override.Memory)
|
||||
} else if override.MemoryLegacy != nil {
|
||||
thresholds.Memory = m.convertLegacyThreshold(override.MemoryLegacy)
|
||||
}
|
||||
if override.Disk != nil {
|
||||
thresholds.Disk = ensureHysteresisThreshold(override.Disk)
|
||||
} else if override.DiskLegacy != nil {
|
||||
thresholds.Disk = m.convertLegacyThreshold(override.DiskLegacy)
|
||||
}
|
||||
if override.DiskRead != nil {
|
||||
thresholds.DiskRead = ensureHysteresisThreshold(override.DiskRead)
|
||||
} else if override.DiskReadLegacy != nil {
|
||||
thresholds.DiskRead = m.convertLegacyThreshold(override.DiskReadLegacy)
|
||||
}
|
||||
if override.DiskWrite != nil {
|
||||
thresholds.DiskWrite = ensureHysteresisThreshold(override.DiskWrite)
|
||||
} else if override.DiskWriteLegacy != nil {
|
||||
thresholds.DiskWrite = m.convertLegacyThreshold(override.DiskWriteLegacy)
|
||||
}
|
||||
if override.NetworkIn != nil {
|
||||
thresholds.NetworkIn = ensureHysteresisThreshold(override.NetworkIn)
|
||||
} else if override.NetworkInLegacy != nil {
|
||||
thresholds.NetworkIn = m.convertLegacyThreshold(override.NetworkInLegacy)
|
||||
}
|
||||
if override.NetworkOut != nil {
|
||||
thresholds.NetworkOut = ensureHysteresisThreshold(override.NetworkOut)
|
||||
} else if override.NetworkOutLegacy != nil {
|
||||
thresholds.NetworkOut = m.convertLegacyThreshold(override.NetworkOutLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
return thresholds
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue