mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-30 11:26:23 +00:00
Update notification_events.py
This commit is contained in:
@@ -1330,32 +1330,36 @@ class TaskWatcher:
|
|||||||
try:
|
try:
|
||||||
# Parse UPID to find log file
|
# Parse UPID to find log file
|
||||||
# UPID format: UPID:node:pid:pstart:starttime:type:id:user:
|
# UPID format: UPID:node:pid:pstart:starttime:type:id:user:
|
||||||
|
# Example: UPID:pve:0000F234:0000B890:67890ABC:qmstart:100:root@pam:
|
||||||
parts = upid.split(':')
|
parts = upid.split(':')
|
||||||
if len(parts) < 5:
|
if len(parts) < 5:
|
||||||
return status
|
return status
|
||||||
|
|
||||||
# Task logs are stored in /var/log/pve/tasks/X/UPID
|
# Task logs are stored in /var/log/pve/tasks/X/UPID
|
||||||
# where X is first char of hex(starttime)
|
# where X is first char of starttime (which is already hex in UPID)
|
||||||
|
# The starttime field (parts[4]) is a hex timestamp
|
||||||
starttime_hex = parts[4]
|
starttime_hex = parts[4]
|
||||||
if starttime_hex:
|
if starttime_hex:
|
||||||
# First character of starttime in hex determines subdirectory
|
# First character of hex starttime determines subdirectory
|
||||||
subdir = starttime_hex[0].upper()
|
subdir = starttime_hex[0].upper()
|
||||||
log_path = os.path.join(self.TASK_DIR, subdir, upid.rstrip(':'))
|
# The log filename is the full UPID without trailing colon
|
||||||
|
upid_clean = upid.rstrip(':')
|
||||||
|
log_path = os.path.join(self.TASK_DIR, subdir, upid_clean)
|
||||||
|
|
||||||
if os.path.exists(log_path):
|
if os.path.exists(log_path):
|
||||||
with open(log_path, 'r', errors='replace') as f:
|
with open(log_path, 'r', errors='replace') as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
# Look for error/warning messages in the log
|
# Look for error/warning messages in the log
|
||||||
# Common patterns: "WARNINGS: ...", "ERROR: ...", "failed: ..."
|
# Common patterns: "warning: ...", "error: ...", "failed: ..."
|
||||||
error_lines = []
|
error_lines = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_lower = line.lower()
|
line_lower = line.lower()
|
||||||
# Skip status lines at the end
|
# Skip status lines at the end (TASK OK, TASK ERROR, etc.)
|
||||||
if line.startswith('TASK '):
|
if line.startswith('TASK '):
|
||||||
continue
|
continue
|
||||||
# Capture warning/error lines
|
# Capture warning/error lines
|
||||||
if any(kw in line_lower for kw in ['warning:', 'error:', 'failed', 'unable to', 'cannot']):
|
if any(kw in line_lower for kw in ['warning:', 'error:', 'failed', 'unable to', 'cannot', 'exception']):
|
||||||
# Clean up the line
|
# Clean up the line
|
||||||
clean_line = line.strip()
|
clean_line = line.strip()
|
||||||
if clean_line and len(clean_line) < 200: # Reasonable length
|
if clean_line and len(clean_line) < 200: # Reasonable length
|
||||||
@@ -1366,7 +1370,8 @@ class TaskWatcher:
|
|||||||
return '; '.join(error_lines[:3])
|
return '; '.join(error_lines[:3])
|
||||||
|
|
||||||
return status
|
return status
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
# Log error for debugging but return status as fallback
|
||||||
return status
|
return status
|
||||||
|
|
||||||
# Map PVE task types to our event types
|
# Map PVE task types to our event types
|
||||||
@@ -1668,9 +1673,9 @@ class TaskWatcher:
|
|||||||
# EXCLUSIVELY by the PVE webhook, which delivers richer data (full
|
# EXCLUSIVELY by the PVE webhook, which delivers richer data (full
|
||||||
# logs, sizes, durations, filenames). TaskWatcher skips these to
|
# logs, sizes, durations, filenames). TaskWatcher skips these to
|
||||||
# avoid duplicates.
|
# avoid duplicates.
|
||||||
# NOTE: backup_start is NOT in this set -- PVE's webhook only fires
|
# NOTE: backup_start and backup_warning are NOT in this set --
|
||||||
# when a backup FINISHES, so TaskWatcher is the only source for
|
# PVE's webhook only fires when backup FINISHES with OK or ERROR,
|
||||||
# the "backup started" notification.
|
# but WARNINGS come through TaskWatcher with richer context.
|
||||||
_WEBHOOK_EXCLUSIVE = {'backup_complete', 'backup_fail',
|
_WEBHOOK_EXCLUSIVE = {'backup_complete', 'backup_fail',
|
||||||
'replication_complete', 'replication_fail'}
|
'replication_complete', 'replication_fail'}
|
||||||
if event_type in _WEBHOOK_EXCLUSIVE:
|
if event_type in _WEBHOOK_EXCLUSIVE:
|
||||||
@@ -1678,26 +1683,28 @@ class TaskWatcher:
|
|||||||
|
|
||||||
# Suppress VM/CT start/stop/shutdown while a vzdump is active.
|
# Suppress VM/CT start/stop/shutdown while a vzdump is active.
|
||||||
# These are backup-induced operations (mode=stop), not user actions.
|
# These are backup-induced operations (mode=stop), not user actions.
|
||||||
# Exception: if a VM/CT FAILS to start after backup, that IS important.
|
# Exception: if a VM/CT FAILS or has WARNINGS, that IS important.
|
||||||
_BACKUP_NOISE = {'vm_start', 'vm_stop', 'vm_shutdown', 'vm_restart',
|
_BACKUP_NOISE = {'vm_start', 'vm_stop', 'vm_shutdown', 'vm_restart',
|
||||||
'ct_start', 'ct_stop', 'ct_shutdown', 'ct_restart'}
|
'ct_start', 'ct_stop', 'ct_shutdown', 'ct_restart'}
|
||||||
if event_type in _BACKUP_NOISE and not is_error:
|
if event_type in _BACKUP_NOISE and not is_error and not is_warning:
|
||||||
if self._is_vzdump_active():
|
if self._is_vzdump_active():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Suppress VM/CT stop/shutdown during host shutdown/reboot.
|
# Suppress VM/CT stop/shutdown during host shutdown/reboot.
|
||||||
# When the host shuts down, all VMs/CTs stop - that's expected behavior,
|
# When the host shuts down, all VMs/CTs stop - that's expected behavior,
|
||||||
# not something that needs individual notifications.
|
# not something that needs individual notifications.
|
||||||
|
# Exception: errors and warnings should still be notified.
|
||||||
_SHUTDOWN_NOISE = {'vm_stop', 'vm_shutdown', 'ct_stop', 'ct_shutdown'}
|
_SHUTDOWN_NOISE = {'vm_stop', 'vm_shutdown', 'ct_stop', 'ct_shutdown'}
|
||||||
if event_type in _SHUTDOWN_NOISE and not is_error:
|
if event_type in _SHUTDOWN_NOISE and not is_error and not is_warning:
|
||||||
if _shared_state.is_host_shutting_down():
|
if _shared_state.is_host_shutting_down():
|
||||||
return
|
return
|
||||||
|
|
||||||
# During startup period, aggregate VM/CT starts into a single message.
|
# During startup period, aggregate VM/CT starts into a single message.
|
||||||
# Instead of N individual "VM X started" messages, collect them and
|
# Instead of N individual "VM X started" messages, collect them and
|
||||||
# let PollingCollector emit one "System startup: X VMs, Y CTs started".
|
# let PollingCollector emit one "System startup: X VMs, Y CTs started".
|
||||||
|
# Exception: errors and warnings should NOT be aggregated - notify immediately.
|
||||||
_STARTUP_EVENTS = {'vm_start', 'ct_start'}
|
_STARTUP_EVENTS = {'vm_start', 'ct_start'}
|
||||||
if event_type in _STARTUP_EVENTS and not is_error:
|
if event_type in _STARTUP_EVENTS and not is_error and not is_warning:
|
||||||
if _shared_state.is_startup_period():
|
if _shared_state.is_startup_period():
|
||||||
vm_type = 'ct' if event_type == 'ct_start' else 'vm'
|
vm_type = 'ct' if event_type == 'ct_start' else 'vm'
|
||||||
_shared_state.add_startup_vm(vmid, vmname or f'ID {vmid}', vm_type)
|
_shared_state.add_startup_vm(vmid, vmname or f'ID {vmid}', vm_type)
|
||||||
|
|||||||
Reference in New Issue
Block a user