Reduced nesting and extracted helper functions to address credo
warnings. The logic remains the same:
- Test environment: synchronous update
- Production: async Task with sandbox support
Fixes credo warnings:
- Reduced function body nesting (was 3, now 2)
- Extracted spawn_async_update and helper functions
The async Task.start pattern was causing DBConnection.Ownership errors
when tests finished before the spawned Task could complete its database
update. By making the update synchronous in test environment, we ensure
the database connection is properly managed within the test process.
In production, we still use the async Task.start to avoid blocking
requests when updating last_used_at timestamps.
Fixes all "owner #PID<...> exited" errors in test suite.
Changes:
- ApiTokens.update_last_used/1: Allow Task to access sandbox in tests
- DeviceMonitor.enqueue_check/1: Allow Task to access sandbox in tests
- PollerWorker.enqueue_poll/1: Allow Task to access sandbox in tests
Problem:
When tests spawn async Tasks that access the database, those Tasks
don't have ownership of the database connection in Sandbox mode,
causing DBConnection.ConnectionError warnings.
Solution:
Before accessing the database in a Task, check if we're in test mode
and call Ecto.Adapters.SQL.Sandbox.allow/3 to transfer connection
ownership from the parent process to the Task.
Pattern used:
```elixir
parent = self()
Task.start(fn ->
if Application.get_env(:towerops, :sql_sandbox) do
Ecto.Adapters.SQL.Sandbox.allow(Repo, parent, self())
end
# ... database operations ...
end)
```
All 1041 tests passing. Some harmless disconnection warnings remain
when Tasks complete after tests finish, but these don't affect results.
Changes:
- Updated MonitorWorker to use configured ping_module adapter instead of System.cmd
- Removed direct ping command execution and regex parsing
- Simplified ping_device/1 to use adapter.ping/2 (mockable in tests)
- Added PingMock expectations to all MonitorWorker tests
- Removed @tag :integration from unreachable host test (now mocked)
- All ping calls now use mocked responses in tests
Benefits:
- MonitorWorker tests now run in 0.2s instead of 5+ seconds
- No real ICMP network traffic during test runs
- Tests are deterministic and don't depend on network conditions
- Follows same adapter pattern as SNMP (testable, configurable)
All 1041 tests passing with no real network operations.
- Add 'error' to valid sensor reading status values
- SensorReading changeset was rejecting 'error' status
- PollWorker creates error readings when SNMP fails
- Fixed validation to include: ok, warning, critical, error
- Replace log capture tests with functional assertions
- ExUnit.CaptureLog not reliably capturing worker logs
- Changed to verify actual behavior instead of log messages
- Tests now check created records and state changes
- Add debug assertions to sensor error test
- Verify SNMP device exists and has sensors
- Better error messages for test failures
All 1045 tests now passing (0 failures, 2 skipped)
Configures Redix (Exq's Redis client) with better connection handling:
- sync_connect: true - fail fast if Redis unavailable at startup
- exit_on_disconnection: false - don't crash process on disconnect
- backoff_initial: 500ms, backoff_max: 30s - retry with exponential backoff
This helps Exq survive temporary Redis disconnections without crashing,
reducing the frequency of Protocol.UndefinedError crashes in Exq.Node.Server.
Fixes crashes in Exq.Node.Server when Redis connections fail by adding
health checks before startup and wrapping Exq in a custom supervisor
with better resilience.
Problem:
- Valkey pod restarting frequently (24 times in 28h) due to K8s Flannel CNI issues
- When Redis disconnects, Exq.Node.Server crashes trying to process_signals/2
- Gets nil from Redis instead of expected list, crashes on Enum.each/2
- Rapid crash/restart cycles reduce application stability
Changes:
1. RedisHealthCheck module (lib/towerops/redis_health_check.ex)
- Waits for Redis availability with exponential backoff before starting Exq
- Prevents Exq from starting when Redis is unavailable
- Handles connection errors gracefully without crashing caller processes
- Supports configurable retry attempts, timeouts, and backoff intervals
2. ExqSupervisor module (lib/towerops/exq_supervisor.ex)
- Custom supervisor that wraps Exq with health checks
- Uses one_for_one strategy with limited restarts (3 per 60s)
- Prevents rapid crash loops when Redis is unstable
- Increased Exq retry/backoff settings for better Redis recovery
- Allows application to continue functioning without background jobs if Redis unavailable
3. Updated Application.ex
- Replaced direct Exq start with ExqSupervisor
- Removed inline exq_config/0 function (moved to supervisor)
4. Tests
- Comprehensive tests for RedisHealthCheck (health checks, retries, timeouts)
- Tests for ExqSupervisor (configuration, restart strategy)
- All 1006 tests passing
Benefits:
- Prevents Exq crashes from propagating when Redis fails
- Application continues running even when Redis is temporarily unavailable
- Better logging of Redis connection issues
- More graceful degradation during infrastructure problems
Infrastructure Issue (to be addressed separately):
- K8s Flannel CNI plugin failing: /run/flannel/subnet.env not found
- Causing Valkey pod network issues and restarts
- Needs investigation of Flannel daemonset and node configuration
🤖 Generated with Claude Code
- Update MibTranslator to automatically expand configured MIB directories
- snmptranslate doesn't search recursively, so we explicitly list all subdirs
- Simplify config to only specify root priv/mibs directory
- Update both config.exs (production) and dev.exs (development)
- Subdirectories like mikrotik/, cisco/, net-snmp/ are auto-discovered
- Remove PVC volume mount from k8s/deployment.yaml
- Delete k8s/mib-pvc.yaml (no longer needed)
- Update .gitignore to commit MIB files to git
- Add mix import_mibs task to copy MIBs from LibreNMS
- Add all MIB files from priv/mibs/ to git
This fixes the multi-attach volume error in Kubernetes where new pods
couldn't start because the RWO (ReadWriteOnce) PVC was attached to the
old pod. MIBs are now part of the Docker image and can be used by all
pods simultaneously.
- Add monitoring_enabled and check_interval_seconds fields to Device and SnmpDevice protobuf messages
- Add MonitoringCheck protobuf message for ping results
- Update AgentChannel to handle monitoring_check events from agents
- Configure monitoring settings in job payloads sent to agents
- Created new on_mount hook :load_default_organization that automatically loads user's first organization
- Moved sites and devices routes to root path (/ sites, /devices) instead of /orgs/:org_slug
- Updated all navigation links and route references throughout the app
- Dashboard, alerts, and agents still use org-specific routes (/orgs/:org_slug)
- Users can still switch organizations via org selector for other pages
The neighbor discovery was using snmp_device.id instead of the actual
Equipment device.id when creating neighbor records. This caused all
neighbor inserts to fail because the foreign key constraint expected
the Equipment ID, not the SNMP Device ID.
Fixed in two places:
- Discovery: Use snmp_device.device_id (FK to Equipment)
- PollerWorker: Use device.id (Equipment ID) instead of snmp_device.id
Neighbors should now be properly discovered and saved during both
initial discovery and ongoing polling.
- Pass range parameter to SensorChart hook via data-range attribute
- Calculate x-axis min/max dynamically based on selected range (1h, 6h, 12h, 24h, 7d, 30d)
- Show date + time on x-axis labels for ranges > 24 hours
- Show time only for ranges <= 24 hours
- Fixes issue where 7d and 30d graphs showed incorrect time range
- Changed ping module to use microsecond timer resolution instead of millisecond
- Prevents 0ms readings for very fast pings (localhost, local network)
- Updated response_time_ms to float type to store decimal precision
- Migration converts existing integer values to float
- Add timezone to socket assigns in mount_current_scope
- Detect user timezone from browser using Intl.DateTimeFormat
- Auto-save detected timezone to user profile on first connection
- Update all templates to use TimeHelpers with timezone parameter
- Update agent helper functions to accept timezone parameter
- Templates updated:
- Alert list (triggered, acknowledged, resolved)
- Device list (last checked)
- Agent list and show (created, last seen, updated)
- Admin dashboard (impersonation logs)
- Admin org/user lists (created/joined dates)
- Main dashboard (alert times)