Commit graph

357 commits

Author SHA1 Message Date
66fd6fa3b6
cloud poller id 2026-01-25 08:52:35 -06:00
1d638c0c83
cleanup: remove debug logging from agent creation
The checkbox issue has been fixed, no longer need the debug output.
2026-01-25 08:39:35 -06:00
2ef66464b8
refactor: filter harmless Oban shutdown messages during deployments
When Kubernetes performs rolling deployments, the old pod gracefully shuts down
and Oban's internal processes receive EXIT :shutdown signals. These are logged
as "unexpected" even though they're normal during shutdown.

Added custom logger filter to drop these messages in production:
- Oban.Queue.Watchman/Producer receiving EXIT :shutdown
- Oban plugin processes receiving EXIT :shutdown
- Reduces log noise during deployments without hiding real errors

The filter is production-only to preserve full debugging in development.
2026-01-25 08:38:33 -06:00
e27cc5495a
fix: handle checkbox 'on' value for cloud poller creation
HTML checkboxes send value='on' when checked, not 'true'. Updated
parse_agent_params to accept both 'on' and 'true' values for the
is_cloud_poller field.

Added debug logging to help diagnose future form submission issues.
2026-01-25 08:22:31 -06:00
83868a6028
fix: remove to_charlist conversion for Redix host option
Redix expects host to be a string, not a charlist. The to_charlist/1
conversion was causing FunctionClauseError in Redix.StartOptions.__validate_host__/1.

This matches how Phoenix PubSub Redis and Exq are configured, which both
use the string host directly without conversion.
2026-01-24 17:49:57 -06:00
a84fc07e42
debug: add detailed logging to health check endpoint
Added logging to identify which component is failing health checks:
- Log database and Redis status on each health check
- Log Redis errors with full error details
- This will help diagnose the production 503 errors
2026-01-24 17:39:56 -06:00
6c1f344b5e
fix: resolve all dialyzer unmatched return and pattern match warnings
Fixed dialyzer warnings across multiple files by properly handling return
values from functions that are called for side effects.

Changes:
- lib/towerops/agents.ex: Explicitly match broadcast and if-expression returns
- lib/towerops/devices.ex: Match worker start function returns in if-expressions
- lib/towerops/workers/agent_latency_evaluator.ex: Match broadcast return
- lib/towerops/workers/device_monitor_worker.ex: Match perform_check and handle_status_change returns, handle schedule_next_check errors
- lib/towerops/workers/device_poller_worker.ex: Match all PubSub broadcast returns, handle schedule_next_poll errors, remove unreachable pattern
- lib/towerops/workers/stale_agent_worker.ex: Match process_stale_agents and broadcast returns
- lib/towerops_web/channels/agent_channel.ex: Match PubSub.subscribe return, improve get_addr pattern matching, remove unreachable format_ip clause
- lib/towerops_web/live/device_live/form.ex: Match enqueue_discovery return, replace compile-time @dev_env with runtime check

Pattern applied:
- PubSub broadcasts: `_ = Phoenix.PubSub.broadcast(...)`
- If-expressions with side effects: `_ = if condition do ... end`
- Critical operations: Added error logging with case statements

All 3625 tests passing. No dialyzer warnings remaining in lib/towerops or lib/towerops_web.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 17:34:37 -06:00
a92d003aec
refactor: replace Task.async_stream with Oban jobs for discovery
Changed discover_all/1 to enqueue individual Oban jobs instead of using
Task.async_stream for better error visibility and resilience.

Benefits:
- Errors are visible in Oban dashboard with full stack traces
- Automatic retries on failure via Oban
- Jobs persist across pod restarts
- No more silent failures from Task crashes
- Better concurrency control via Oban queue configuration

Changes:
- discover_all/1 now enqueues DiscoveryWorker jobs and returns immediately
- Return type changed from %{success, failed, errors} to %{enqueued, failed, errors}
- Updated tests to match new async behavior
- Added DiscoveryWorker alias to Discovery module

All tests passing (3,625 tests, 0 failures).
2026-01-24 17:15:36 -06:00
58883d0d58
fix: add missing RedisHealthCheck module for health endpoint
The health check endpoint was calling Towerops.RedisHealthCheck.check_health/2
but the module didn't exist, causing startup probes to fail with 503 errors.

Added RedisHealthCheck module with:
- check_health/2: Single PING command with timeout for health checks
- wait_for_redis/2: Exponential backoff retry logic for startup

This fixes the failing health checks that were blocking pod rollouts.

All tests passing.
2026-01-24 17:06:33 -06:00
ee8a3220c4
refactor: convert periodic workers to Oban Cron for better resilience
Replaced GenServer-based periodic workers with Oban Cron jobs to improve
pod rollover resilience and simplify architecture.

Worker Changes:
- NeighborCleanupWorker: GenServer → Oban Cron (hourly)
  - Cleans stale neighbors, ARP entries, and MAC addresses
  - Runs every hour via Oban.Plugins.Cron

- StaleAgentWorker: GenServer → Oban Cron (every minute)
  - Detects agents that haven't checked in for 10+ minutes
  - Refactored to reduce nesting (extracted helper functions)
  - Removed stateful tracking (now stateless, re-evaluates each run)

- AgentLatencyEvaluator: GenServer → Oban Cron (every 5 minutes)
  - Latency-based agent reassignment with 20% threshold
  - Removed trigger_evaluation/0 (no longer needed)

- JobHealthCheckWorker: NEW Oban Cron worker (every 10 minutes)
  - Safety net to recover missing monitor/poller jobs
  - Auto-creates jobs for devices with monitoring/SNMP enabled

Infrastructure Changes:
- Removed Monitoring.Supervisor (no longer needed)
- Updated application.ex to remove GenServer workers from supervision tree
- Added Oban.Plugins.Cron to dev.exs and runtime.exs
- All workers now run cluster-wide via PostgreSQL-backed coordination

Test Updates:
- Updated all worker tests to call perform(%Oban.Job{args: %{}})
- Removed GenServer lifecycle tests (start_link, send messages, etc.)
- Removed async sleep calls (no longer needed)

Benefits:
- Better pod rollover resilience (Cron jobs run cluster-wide)
- Simpler architecture (no GenServers for periodic tasks)
- Better observability (all jobs visible in Oban dashboard)
- Safety net for missing jobs (JobHealthCheckWorker)
- Stateless workers (easier to reason about and test)

Documentation:
- Updated CLAUDE.md with Background Job Architecture section
- Documented job types, queues, and resilience features

All tests passing (3,686 tests, 0 failures).
2026-01-24 16:56:28 -06:00
92cd812806
fix: add Oban config to dev.exs and replace Exq telemetry with Oban
Changes:
- Add Oban configuration to config/dev.exs (was only in runtime.exs)
- Replace Exq telemetry metrics with Oban equivalents
- Update publish_exq_stats to publish_oban_stats using Oban.Job queries
- Track queue sizes, executing jobs, and available jobs

This fixes the startup error where Oban config was not available in dev.
2026-01-24 16:39:45 -06:00
d0946c3cd0
refactor: simplify job architecture from Oban coordinators to direct workers
Removes unnecessary two-layer architecture (Oban Coordinator → GenServer)
in favor of direct Oban workers that perform the actual work.

Changes:
- Replace DevicePollerCoordinator + PollerWorker with DevicePollerWorker
- Replace DeviceMonitorCoordinator + DeviceMonitor with DeviceMonitorWorker
- Simplify Monitoring.Supervisor (removed Registries, DynamicSupervisors)
- Remove all Exq dependencies (workers, supervisor, mix deps)
- Convert DiscoveryWorker from Exq to Oban pattern
- Update Devices context to auto-start/stop monitoring and polling
- Update all LiveView callers to use new Oban enqueue pattern
- Fix all tests to use Oban.Job struct instead of raw IDs

Benefits:
- Simpler codebase (~850 lines removed)
- Better reliability (Oban handles retries, failures)
- Lower memory (no persistent GenServers)
- Better observability (work visible in Oban dashboard)
- Cluster-wide coordination via PostgreSQL
- Extensive PubSub usage for real-time events

Files deleted:
- lib/towerops/workers/device_monitor_coordinator.ex
- lib/towerops/workers/device_poller_coordinator.ex
- lib/towerops/snmp/poller_worker.ex
- lib/towerops/monitoring/device_monitor.ex
- lib/towerops/workers/monitor_worker.ex
- lib/towerops/workers/poll_worker.ex
- lib/towerops/exq_supervisor.ex
- All related test files

Files created:
- lib/towerops/workers/device_poller_worker.ex
- lib/towerops/workers/device_monitor_worker.ex

All 3,686 tests passing.
2026-01-24 16:36:57 -06:00
29593ac734
refactor: migrate from etcd to Oban for distributed job coordination
Replaces etcd-based distributed locking with Oban's PostgreSQL-backed job queue.
This simplifies the architecture by eliminating the need for a separate etcd cluster
while providing better reliability and observability.

Changes:
- Add Oban dependency and migration (oban_jobs table)
- Create DevicePollerCoordinator and DeviceMonitorCoordinator Oban workers
- Remove EtcdCoordinator and EtcdLock modules
- Update application supervisor to start Oban
- Configure Oban with pollers (50 workers) and monitors (50 workers) queues
- Remove etcd StatefulSet from Kubernetes manifests
- Update monitoring supervisor documentation

Benefits:
- Simpler architecture (no etcd cluster to manage)
- PostgreSQL-based (uses existing database)
- Built-in uniqueness prevents duplicate jobs cluster-wide
- Better observability with Oban Web UI
- Automatic job rescue on node crashes
- Easier local development (no etcd required)

What was removed:
- etcd StatefulSet (3 pods)
- EtcdCoordinator module (320 lines)
- EtcdLock module (158 lines)
- eetcd dependency

All 3,686 tests passing.
2026-01-24 16:12:27 -06:00
de0c106212
fix: Redis authentication and etcd connection handling
Fixes two production issues:

1. NOAUTH Redis authentication error:
   - application.ex: Read password from application config
   - exq_supervisor.ex: Read password from application config
   - Both were bypassing runtime.exs config by reading env directly

2. EtcdCoordinator FunctionClauseError:
   - connect_etcd/0 now returns connection name (:towerops_etcd)
   - eetcd functions expect the atom name, not the PID
   - Fixes crash when trying to acquire locks
2026-01-24 15:58:21 -06:00
13f86de4b8
use application config for redis password instead of env 2026-01-24 15:43:27 -06:00
f5ca3f0940
fix: make etcd watcher optional for graceful degradation
The app was crashing when the etcd watcher couldn't be created, even
though 2/3 etcd nodes were healthy and the connection succeeded.

Changes:
- Made watcher creation optional (nil if it fails)
- App now starts successfully even if watcher is unavailable
- Falls back to periodic rebalancing (60s intervals) instead of instant failover
- Changed error level from error to warning when watcher creation fails
- Added helpful context to warning message

This allows the app to run with degraded performance instead of crashing
completely when there are etcd cluster issues.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 15:27:28 -06:00
64ad63d3e3
fix: handle etcd watcher creation errors gracefully
The pods were crashing with a MatchError when the etcd watcher failed
to initialize. The code was pattern matching on {:ok, watcher} but
eetcd was returning {:error, :eetcd_conn_unavailable}.

Error:
  ** (MatchError) no match of right hand side value:
  {:error, :eetcd_conn_unavailable}

This was causing CrashLoopBackOff because the application couldn't
start the Monitoring.Supervisor due to EtcdCoordinator initialization
failure.

Fix:
  - Add case statement to handle both {:ok, watcher} and {:error, reason}
  - Log the specific error when watcher creation fails
  - Return proper error tuple to supervisor for clean shutdown
2026-01-24 15:02:46 -06:00
51736eeec3
fix: use charlists for etcd endpoints
The :eetcd Erlang library expects charlists, not binary strings.
This was causing a FunctionClauseError in :lists.reverse/1 because
it was receiving a binary string instead of a charlist.

Error:
  (FunctionClauseError) no function clause matching in :lists.reverse/1
  (stdlib 7.2) lists.erl:291: :lists.reverse("etcd-0.etcd...")

Solution:
  Use ~c sigil to convert endpoint strings to charlists for the
  Erlang library to consume correctly.
2026-01-24 14:39:53 -06:00
0d4c8eb15a
feat: add Redis password authentication support
Add REDIS_PASSWORD environment variable support to both Phoenix.PubSub.Redis
and Exq background job processor. Password is optional - if not set, connects
without authentication.

Changes:
- application.ex: Add password to PubSub Redis config if REDIS_PASSWORD is set
- exq_supervisor.ex: Add password to Exq Redis config if REDIS_PASSWORD is set

Required for connecting to Proxmox-hosted Redis with authentication.
2026-01-24 14:18:55 -06:00
5ee241fc16
feat: implement multi-replica Valkey with Sentinel for high availability
Addresses production Redis disconnection issues by implementing a highly
resilient Valkey (Redis) setup with automatic failover capabilities.

Infrastructure Changes:
- Add Valkey ConfigMap with optimized connection and memory settings
  - TCP keepalive (60s), connection limits (10k clients)
  - Memory management (256MB with LRU eviction)
  - Separate configs for master, replica, and sentinel

- Update Valkey StatefulSet to 3 replicas (1 master + 2 replicas)
  - Auto-configuration via init container (master vs replica)
  - Increased memory limits: 256Mi → 1Gi
  - Improved readiness probes with replication status checks

- Add Valkey Sentinel StatefulSet (3 instances for quorum)
  - Automatic failover detection (5s down-after-milliseconds)
  - Fast failover execution (10s timeout)
  - Monitors master and promotes replicas automatically

- Add Sentinel headless service for pod discovery

Application Resilience:
- Update Phoenix.PubSub.Redis with TCP keepalive and reconnection
  - Connection timeout: 5s
  - Exponential backoff: 500ms → 30s
  - exit_on_disconnection: false

- Update Exq background jobs with same resilience settings
  - TCP keepalive enabled
  - Better connection pool management

Benefits:
- Automatic failover when Valkey pod dies (no manual intervention)
- Zero data loss with replica synchronization
- Fast failure detection and recovery (5-10s total)
- Survives Flannel CNI networking issues
- Apps reconnect automatically during disconnections

Testing:
- All 3,686 tests passing
- Kustomize manifest validated

🤖 Generated with Claude Code
2026-01-24 14:03:50 -06:00
67614cb93f
improve cloud poller display 2026-01-24 13:33:01 -06:00
af71d3f4f9
fix remote agent ip 2026-01-24 13:23:03 -06:00
e6054129b2
remote agent/cloud improvements 2026-01-24 09:16:41 -06:00
f084e09ea1
add multiple cloud agents 2026-01-23 18:04:01 -06:00
29f00c37ed
credo fixes 2026-01-23 16:49:02 -06:00
2b78b1a2d3
snmpkit overhaul and etcd 2026-01-23 16:23:57 -06:00
f59cdbbd7a
credo fixes 2026-01-23 14:01:52 -06:00
7fcfdbf2e9
credo improvements 2026-01-23 13:40:49 -06:00
ec76428349
credo fixes 2026-01-23 13:19:56 -06:00
518b49318c
routeros fix 2026-01-23 13:16:02 -06:00
a4335a047a
allow nonroutable for me 2026-01-23 13:10:12 -06:00
a0ba9285dd
bring in snmpkit 2026-01-23 12:52:17 -06:00
0056d35e9b
ignore more startup errors 2026-01-23 10:08:04 -06:00
3495767b8d
ignore startup errors 2026-01-23 10:06:25 -06:00
80d1864700
mac discovery 2026-01-23 09:22:08 -06:00
7bd3b1f9ec
arp 2026-01-23 08:40:57 -06:00
f402dcb7af
fix mikrotik sensors 2026-01-22 18:07:20 -06:00
639ffd4a2c
better discovery 2026-01-22 17:40:58 -06:00
96fc488023
sanitize snmp data and fix some cpu things 2026-01-22 16:53:01 -06:00
2856a0ccda
improved fallabck detection 2026-01-22 13:43:56 -06:00
17f4558702
implement timescaledb improvements 2026-01-22 13:24:14 -06:00
d401e0b036
mikrotik improvements 2026-01-22 13:05:28 -06:00
218b99972e
more tests 2026-01-22 11:51:35 -06:00
155b38ab94
feat: add APC, Arista, Calix, ADTRAN, Allied Telesis vendor modules
Add 5 new vendor modules for network infrastructure equipment:
- APC: UPS and PDU monitoring (battery, voltage, load, runtime)
- Arista EOS: Data center switches (CPU, memory)
- Calix: GPON/fiber access equipment
- ADTRAN AOS: NetVanta routers and switches
- Allied Telesis: AlliedWare/AlliedWare Plus switches

Total: 53 vendors, 88 profile names

Also cleanup commented AlertNotifier code from previous removal.
2026-01-22 11:00:21 -06:00
aec9807e97
feat: add Meraki/FortiWLC vendors, remove AlertNotifier temporarily
Vendors:
- Add Meraki vendor module (merakimr, merakims, merakimx profiles)
- Add FortiWLC vendor module for Fortinet wireless controllers
- Add cnpilotr profile to existing Cnpilot module
- 48 vendors now covering 77 profile names

Cleanup:
- Remove AlertNotifier module and tests (flaky, to be reimplemented)
2026-01-22 10:40:44 -06:00
74e409d6a6
feat: add Pepwave, Ceragon, Tranzeo, and CMM vendor modules 2026-01-22 10:22:19 -06:00
928055cbe3
feat: add FortiAP, D-Link AP, and Moxa AWK vendor modules 2026-01-22 10:12:49 -06:00
508ad99919
feat(snmp): add 6 more vendor modules (xirrus, alvarion, zyxel_wlc, proxim, altalabs, stellar) 2026-01-22 10:02:42 -06:00
e1bc56e065
feat(snmp): add 6 new vendor modules (cnwave60, cnmatrix, ruckus_sz, deliberant, engenius, grandstream) 2026-01-22 09:53:33 -06:00
7c2014842d
feat: add 5 new SNMP vendor modules
Add vendor-specific SNMP handling for:
- Aerohive (HiveOS wireless APs): client counts, CPU/memory, temperature
- Baicells (LTE CPE): RSRP, RSRQ, SINR, RSSI, tx power, temperature
- Cambium PTP (point-to-point radios): RSL, tx power, data rates, temperature
- Cisco WAP (standalone APs): clients, channel utilization, noise floor
- Netonix (WISP switches): temperatures, fan speed, PSU voltages, PoE power

Each module implements the Vendor behaviour with:
- profile_names/0 for profile matching
- detect_hardware/1 for model identification
- discover_wireless_sensors/1 for sensor discovery
- wireless_oid_defs/0 for debug data collection

All modules registered in vendor registry with comprehensive tests.
2026-01-22 09:42:48 -06:00