Fix duplicate ticket creation for evolving SMART errors

Problem: When SMART errors evolved on the same drive, new tickets were created
instead of updating the existing ticket. This happened because the hash was
based on specific error values (e.g., "Reallocated_Sector_Ct: 8") instead of
just the issue category.

Root Cause:
- Old hash included specific SMART attribute names and values
- When errors changed (8 → 16 reallocated sectors, or new errors appeared),
  the hash changed, allowing duplicate tickets
- Only matched "Warning" attributes, missing "Critical" and "Error X occurred"
- Only matched /dev/sd[a-z], missing NVMe devices

Solution:
- Hash now based on: hostname + device + issue_category (e.g., "smart")
- Does NOT include specific error values or attribute names
- Supports both /dev/sdX and /dev/nvmeXnY devices
- Detects issue categories: smart, storage, memory, cpu, network

Result:
 Same drive, errors evolve → Same hash → Updates existing ticket
 Different device → Different hash → New ticket
 Drive replaced → Different device → New ticket
 NVMe devices now supported

Example:
Before:
- "Warning Reallocated: 8" → hash abc123
- "Warning Reallocated: 16" → hash xyz789 (NEW TICKET - bad!)

After:
- "Warning Reallocated: 8" → hash abc123
- "Warning Reallocated: 16" → hash abc123 (SAME TICKET - good!)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 19:27:13 -05:00
parent 80db9d76f8
commit de9da756e9

View File

@@ -84,22 +84,32 @@ $data = json_decode($rawInput, true);
// Generate hash from stable components // Generate hash from stable components
function generateTicketHash($data) { function generateTicketHash($data) {
// Extract device name if present (matches /dev/sdX pattern) // Extract device name if present (matches /dev/sdX, /dev/nvmeXnY patterns)
preg_match('/\/dev\/sd[a-z]/', $data['title'], $deviceMatches); preg_match('/\/dev\/(sd[a-z]|nvme\d+n\d+)/', $data['title'], $deviceMatches);
$isDriveTicket = !empty($deviceMatches); $isDriveTicket = !empty($deviceMatches);
// Extract hostname from title [hostname][tags]... // Extract hostname from title [hostname][tags]...
preg_match('/\[([\w\d-]+)\]/', $data['title'], $hostMatches); preg_match('/\[([\w\d-]+)\]/', $data['title'], $hostMatches);
$hostname = $hostMatches[1] ?? ''; $hostname = $hostMatches[1] ?? '';
// Extract SMART attribute types without their values // Detect issue category (not specific attribute values)
preg_match_all('/Warning ([^:]+)/', $data['title'], $smartMatches); $issueCategory = '';
$smartAttributes = $smartMatches[1] ?? []; if (stripos($data['title'], 'SMART issues') !== false) {
$issueCategory = 'smart';
} elseif (stripos($data['title'], 'LXC') !== false || stripos($data['title'], 'storage usage') !== false) {
$issueCategory = 'storage';
} elseif (stripos($data['title'], 'memory') !== false) {
$issueCategory = 'memory';
} elseif (stripos($data['title'], 'cpu') !== false) {
$issueCategory = 'cpu';
} elseif (stripos($data['title'], 'network') !== false) {
$issueCategory = 'network';
}
// Build stable components with only static data // Build stable components with only static data
$stableComponents = [ $stableComponents = [
'hostname' => $hostname, 'hostname' => $hostname,
'smart_attributes' => $smartAttributes, 'issue_category' => $issueCategory, // Generic category, not specific errors
'environment_tags' => array_filter( 'environment_tags' => array_filter(
explode('][', $data['title']), explode('][', $data['title']),
fn($tag) => in_array($tag, ['production', 'development', 'staging', 'single-node']) fn($tag) => in_array($tag, ['production', 'development', 'staging', 'single-node'])
@@ -112,7 +122,6 @@ function generateTicketHash($data) {
} }
// Sort arrays for consistent hashing // Sort arrays for consistent hashing
sort($stableComponents['smart_attributes']);
sort($stableComponents['environment_tags']); sort($stableComponents['environment_tags']);
return hash('sha256', json_encode($stableComponents, JSON_UNESCAPED_SLASHES)); return hash('sha256', json_encode($stableComponents, JSON_UNESCAPED_SLASHES));