towerops/lib/mix/tasks/backfill_checks.ex
Graham McIntire 75d64d8772
feat: add check creation from discovery and backfill tool (Phase 5)
Completes Phase 5 of the unified checks system by enabling automatic check
creation during SNMP discovery and providing a backfill tool for existing devices.

Discovery Integration:
- create_checks_from_discovery/2: Creates checks for all discovered entities
  (sensors, interfaces, processors, storage) after SNMP discovery completes
- Links each check to its source entity via source_id for data lookup
- Returns detailed results with counts per type and error tracking
- Logs creation results with total counts and any failures

Helper Functions:
- create_sensor_check/2: Creates snmp_sensor checks with config
- create_interface_check/2: Creates snmp_interface checks
- create_processor_check/2: Creates snmp_processor checks
- create_storage_check/2: Creates snmp_storage checks

All checks configured with:
- 60-second poll intervals (default)
- Auto-discovery source type
- Enabled by default
- Type-specific configuration in JSONB config field

Backfill Tool:
- mix backfill.checks: Creates checks for existing SNMP-enabled devices
- Supports --dry-run mode for preview
- Shows counts per device (sensors, interfaces, processors, storage)
- Reports success/failure counts and detailed errors
- Safely handles devices without discovery data
- Reduced nesting depth using cond and helper functions for credo compliance

This enables the unified monitoring system to work with both new discoveries
and existing devices, ensuring all SNMP data can be monitored through the
unified checks interface.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 17:04:06 -06:00

103 lines
2.9 KiB
Elixir

defmodule Mix.Tasks.Backfill.Checks do
@shortdoc "Backfill checks for existing SNMP-enabled devices"
@moduledoc """
Backfill checks for existing devices with SNMP discovery data.
This task creates check records for all devices that have existing
snmp_device records with sensors, interfaces, processors, or storage.
Usage:
mix backfill.checks
mix backfill.checks --dry-run
"""
use Mix.Task
import Ecto.Query
alias Towerops.Devices
alias Towerops.Repo
alias Towerops.Snmp.Discovery
require Logger
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
dry_run = "--dry-run" in args
if dry_run do
IO.puts("=== DRY RUN MODE - No changes will be made ===\n")
end
devices =
Repo.all(
from d in Devices.Device,
where: d.snmp_enabled == true,
preload: [:organization, snmp_device: [:sensors, :interfaces, :processors, :storage]]
)
IO.puts("Found #{length(devices)} SNMP-enabled devices\n")
Enum.each(devices, fn device ->
if device.snmp_device do
backfill_device_checks(device, dry_run)
else
IO.puts("Skipping #{device.name} - no SNMP discovery data")
end
end)
IO.puts("\n=== Backfill complete ===")
end
defp backfill_device_checks(device, dry_run) do
snmp_device = device.snmp_device
sensor_count = length(snmp_device.sensors)
interface_count = length(snmp_device.interfaces)
processor_count = length(snmp_device.processors)
storage_count = length(snmp_device.storage)
total = sensor_count + interface_count + processor_count + storage_count
cond do
total == 0 ->
IO.puts("Skipping #{device.name} - no discovered entities")
dry_run ->
print_device_summary(device, sensor_count, interface_count, processor_count, storage_count)
IO.puts(" [DRY RUN] Would create #{total} checks")
true ->
print_device_summary(device, sensor_count, interface_count, processor_count, storage_count)
create_and_report_checks(device, snmp_device)
end
end
defp print_device_summary(device, sensors, interfaces, processors, storage) do
IO.puts("\n#{device.name} (#{device.ip_address}):")
IO.puts(" - #{sensors} sensors")
IO.puts(" - #{interfaces} interfaces")
IO.puts(" - #{processors} processors")
IO.puts(" - #{storage} storage")
end
defp create_and_report_checks(device, snmp_device) do
result = Discovery.create_checks_from_discovery(device, snmp_device)
created = result.sensors + result.interfaces + result.processors + result.storage
if result.errors == [] do
IO.puts(" ✓ Created #{created} checks")
else
IO.puts(" ✓ Created #{created} checks (#{length(result.errors)} errors)")
print_errors(result.errors)
end
end
defp print_errors(errors) do
Enum.each(errors, fn error ->
IO.puts("#{error.type}: #{error.message}")
end)
end
end