Optimized the slowest tests to improve test suite performance:
1. Property-based IP validation test (6s → ~2.4s)
- Reduced max_runs from 50 to 20 iterations
- Still provides good coverage while being much faster
2. Database-heavy tests (~1s each)
- Replaced individual create_check calls with bulk Repo.insert_all
- Reduced check counts from 15 to 10 where adequate for testing
- Fixed timestamp handling (truncate to :second for Ecto)
- Fixed response_time_ms to use floats (schema requirement)
- Removed updated_at (disabled in Check schema)
- Tests affected:
* get_device_latency_by_agent/2
* get_uptime_percentage/1
* get_latency_data/2 respects limit
* AgentLatencyEvaluator tests
Expected speedup: ~40% reduction in top 10 slowest tests (12.8s → ~7.7s)
These optimizations maintain test coverage while significantly reducing
database transaction overhead.
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
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.
Redis authentication was failing with "NOAUTH Authentication required"
because the password wasn't being read from the environment variable.
Changes:
- Read REDIS_PASSWORD from environment in runtime.exs
- Add password to :redis config keyword list if present
- Phoenix.PubSub.Redis and Exq now read password from Application config
The application.ex and exq_supervisor.ex already had password support
from System.get_env("REDIS_PASSWORD"), but runtime.exs wasn't passing
the password through to the :redis config, so it was never available
to the application code.
This enables connection to the new Proxmox-hosted Valkey instance at
10.0.15.21 with authentication.
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.
Removing all Valkey (Redis) resources from Kubernetes due to instability
caused by Flannel CNI networking issues. Redis will now run on Proxmox
hosts for better stability and performance.
Changes:
- Delete Valkey StatefulSet (master + 2 replicas)
- Delete Valkey Sentinel StatefulSet (3 instances)
- Delete Valkey services (headless and sentinel)
- Delete Valkey ConfigMap
- Remove Valkey resources from kustomization.yaml
- Update deployment to use towerops-redis secret for connection
Next Steps:
- Set up Redis Sentinel on 3 Proxmox hosts/LXC containers
- Create towerops-redis secret with REDIS_HOST and REDIS_PORT
- Test failover and application connectivity
Benefits:
- Not affected by K8s networking issues (Flannel failures)
- More stable (no restarts from node issues)
- Better performance (no K8s overhead)
- Independent lifecycle from K8s cluster
Sentinel requires individual pod DNS names to work correctly.
Making the valkey service headless enables DNS resolution for:
- valkey-0.valkey.towerops.svc.cluster.local
- valkey-1.valkey.towerops.svc.cluster.local
- valkey-2.valkey.towerops.svc.cluster.local
This fixes CrashLoopBackOff in valkey-sentinel pods.
Add 8 new vendor modules for wireless sensor discovery:
- PMP: Cambium PMP 450/430/100 and PTP series
- Mimosa: B5/B5c PTP and A5/A5c PTMP radios
- Siklu: EtherHaul mmWave radios
- Radwin: WinLink and JET series
- UniFi: Ubiquiti UniFi Access Points
- LigoOS: LigoWave and Deliberant devices
- cnPilot: Cambium cnPilot E and R series
- SAF: SAF Tehnika Integra/Lumina/CFIP series
Each module implements the Vendor behaviour with:
- profile_names/0: OS profile identifiers
- detect_hardware/1: device model detection
- wireless_oid_defs/0: sensor OID definitions
- discover_wireless_sensors/1: sensor discovery
Includes comprehensive tests for all modules (104 tests).
Mirrors LibreNMS architecture where vendor-specific discovery logic
lives in OS PHP classes. Creates modular vendor system with:
- Vendor behaviour defining callbacks for profile_names, detect_hardware,
discover_wireless_sensors, wireless_oid_defs
- Epmp vendor module for Cambium ePMP devices
- Airos vendor module for Ubiquiti AirOS devices
- Routeros vendor module for MikroTik devices with table-based discovery
- Registry module for dispatching to appropriate vendor by profile name
Removes 250+ lines of inline vendor code from dynamic.ex.
- Change sensor_value to last_value to match Sensor schema
- Make wireless sensor_index unique using OID suffix
- Values now persist and can be graphed