- use 64-bit HC counters (ifHCInOctets/ifHCOutOctets) instead of 32-bit
counters that wrap every ~34s on gigabit links
- fix negative integer parsing in replay adapter so sensor scale values
like -3 produce correct divisors (1000) instead of falling through to 1
- replace walking entire Cisco enterprise tree with targeted subtrees to
avoid timeouts; add Juniper, HP/H3C, Fortinet, Cambium vendor OIDs
- add missing discovery OIDs: ENTITY-STATE-MIB, HOST-RESOURCES-MIB
tables, and UCD-SNMP-MIB Linux CPU stats fallback
Some MikroTik devices return the version OID as a list of integers
[7, 20, 6] instead of a binary string "7.20.6". Added pattern match
to detect_version/1 to handle this case and convert to dotted string.
Fixes warning: "Failed to detect version: {:ok, [7, 20, 6]}"
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
strip leading dots from OID keys returned by agent so they match
sensor OIDs stored without leading dots. fixes ubiquiti airos
wireless sensors (clients, ccq, frequency, etc) not being polled.
also fix flaky sensor_change_detector tests using global pubsub topic.
extract sensor change detection from DevicePollerWorker into shared
SensorChangeDetector module. add sensor_value_changed event for non-%
sensors when value differs from last_value. agent channel now detects
changes and updates last_value, matching phoenix poller behavior.
YAML parses bare numbers like `index: 0` as integers. coerce
index_template to string before calling String.replace in both
build_table_sensor and build_state_sensor.
newly discovered devices sat idle with no metric data until the
next scheduling cycle. now the channel sends a poll job (and ping
job if monitoring enabled) right after discovery succeeds, using
send(self(), ...) so discovery DB writes commit first.
also reduces k8s replicas from 3 to 2.
gosnmp returns OIDs with leading dots (e.g., ".1.3.6.1.2.1.1.1.0")
but the Replay adapter did exact-match lookups without the dot.
Walks already normalized both sides, so interfaces worked. GETs
did not, breaking all system OID resolution (sysDescr, sysName, etc).
- increase timing threshold in session activity test (flaky at 50ms)
- show org name instead of agent ID in admin agents list
- fix agent live tests wiping auth session with init_test_session
- add resilient error handling in FourOhFourTracker for missing redis
the version regex only allowed [a-zA-Z0-9.] in semver suffixes,
which rejected hyphens from git-describe output (e.g. 0.1.0-5-gabcdef0).
this caused every heartbeat to fail validation, preventing last_seen_at
from updating, which led to agent status showing "warning" and eventual
channel disconnect after 360s.
also accept bare git SHAs and short identifiers like "dev".
Replace phx-click JS.navigate with real <.link navigate> elements
on table rows so users can Ctrl+Click to open in new tabs, use
right-click context menus, and navigate via keyboard. Adds row_link
attr to <.table> core component and applies it to all agent tables.
Converts device list custom table from phx-click to inner links.
Broadcast org-scoped PubSub events from Devices context mutations
(create, update, delete, status change). Convert DeviceLive.Index
from assigns to streams with flat row items. Subscribe on mount so
the device list updates automatically across browser tabs.
POST /api/v1/webhooks/agent-release triggers all connected agents
to self-update via their existing WebSocket channels. Authenticated
with a shared secret (AGENT_WEBHOOK_SECRET env var).
- Add ReleaseChecker.invalidate_cache/0 for fresh GitHub fetch
- Add Agents.list_updatable_agents/0 (enabled + seen < 10min)
- Add Agents.broadcast_mass_update/0 orchestration function
- Add WebhookAuth plug with timing-safe secret comparison
- Add AgentReleaseWebhookController with :webhook pipeline
- Configure AGENT_WEBHOOK_SECRET in dev/test/runtime configs
Add server-initiated restart and self-update capabilities for remote
agents. Restart broadcasts on the lifecycle PubSub topic causing the
agent to exit cleanly for Docker to restart. Self-update fetches the
latest release from GitHub, finds the matching binary for the agent's
architecture, and sends the download URL with SHA256 checksum.
- Add restart_agent/1 and update_agent/3 to Agents context
- Handle :restart_requested and :update_requested in AgentChannel
- Add superuser-only Restart and Update buttons on agent show page
- Add ReleaseChecker module for GitHub Releases API with 5-min cache
- Add arch field to AgentHeartbeat protobuf for architecture detection
- Store arch in agent metadata from heartbeat
The admin agents page links to /agents/:id but the show and edit
pages were scoped to the current user's organization, causing 404s
for agents belonging to other orgs.
Broadcast agent lifecycle events (created/deleted) on a separate
"admin:agents" topic so the admin page updates dynamically when
agents are added or removed from any organization.
Superuser-only page showing all agents across all organizations
and cloud pollers in a single table with live-ticking timestamps
and real-time status updates via PubSub.
- Add Towerops.TaskSupervisor to supervision tree, replace bare spawn/1
in agent channel with supervised tasks for polling data processing
- Change DiscoveryWorker from max_attempts: 1 to 3 with retryable error
classification (transient errors retry, permanent errors discard)
- Name all 9 parallel polling tasks and log timeouts/crashes instead of
silently dropping results via Stream.run()
- Add tests for TaskSupervisor presence and error classification
This commit addresses Phase 1 (CRITICAL - Data Loss Prevention) issues
from the comprehensive bugs and inconsistencies analysis.
CRITICAL FIXES:
1. Protobuf Schema Inconsistencies
- Added missing job_id field (field 5) to SnmpResult message
- Fixed AgentError message structure (job_id, message, timestamp)
- Removed obsolete transport field from SnmpDevice struct
- Regenerated protobuf code with protoc-gen-elixir
- Prevents crashes when agent sends SNMP results or errors
2. Interface/Sensor Discovery Timeout Data Loss
- Changed timeout behavior to return error instead of empty list
- Prevents deletion of ALL existing interfaces/sensors on slow networks
- Discovery fails gracefully instead of destroying historical data
- Addresses commit 7a57f7c sensor discovery pipeline data loss issue
3. Polling Offset Schedule Mismatch
- Fixed schedule_next_poll() to use offset only (not interval + offset)
- Maintains consistent polling intervals across all executions
- Prevents load bunching that offset was designed to prevent
- Example: 300s interval with offset=94s now consistently polls every 5m
4. Always Reschedule Race Condition
- Added should_continue_polling?() and should_continue_monitoring?()
- Only reschedule if device is still enabled and should be polled by Phoenix
- Prevents zombie jobs from continuing to poll disabled/deleted/reassigned devices
- Stops race condition where stop_polling() is bypassed by in-flight jobs
Files Changed:
- priv/proto/agent.proto
- lib/towerops/proto/agent.pb.ex (regenerated)
- lib/towerops_web/channels/agent_channel.ex
- lib/towerops/snmp/discovery.ex
- lib/towerops/workers/device_poller_worker.ex
- lib/towerops/workers/device_monitor_worker.ex
- test/towerops/workers/device_poller_worker_test.exs
Test Results: All passing
- device_poller_worker_test.exs: 8 tests, 0 failures
- device_monitor_worker_test.exs: 3 tests, 0 failures
- discovery_test.exs: 13 tests, 0 failures
Remaining Phases:
- Phase 2 (HIGH): 8 data integrity issues
- Phase 3 (MEDIUM): 10 reliability issues
- Phase 4 (LOW): 3 cleanup issues
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added integration tests for real-time updates and metrics display.
Also fixed dialyzer warnings in worker event broadcasting by removing
unreachable error handling branches.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add LiveView for real-time job monitoring dashboard:
- Subscribe to job:lifecycle PubSub topic for live updates
- Display health metrics (completed, failed, avg duration, active jobs)
- Show active operations with real-time updates
- Display problems section (stuck/failed job counts)
- Add router entry under /admin/monitoring
Tests verify PubSub subscription and basic rendering.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Create JobMonitoring.Metrics module with calculate_all/0 to provide
comprehensive metrics including execution counts, success rates,
queue depths, and average execution times.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add list_failed_jobs/0 to return retryable and cancelled jobs
and list_recent_completions/1 to return completed, cancelled,
or discarded jobs with configurable limit.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes discovery failures on enterprise network devices with many
interfaces and routing table entries.
Issue:
Discovery was failing with "too_many_oids - OID values map exceeds
1000 entries" on SNMPv3 devices. Enterprise routers/switches during
discovery can easily return 10k-50k OIDs from:
- 100+ interfaces with 20-30 OIDs each
- System info, routing tables, ARP tables, neighbor tables
- Various MIB objects
The original limit of 1,000 was too restrictive for real-world devices
while still being intended as DoS protection.
Changes:
- Increase @max_oid_values from 1,000 to 50,000
- Update test to generate 50,100 OIDs (exceeds new limit)
- Add comment explaining why limit is set to 50k
This maintains DoS protection (prevents multi-million OID attacks)
while accommodating realistic device discovery scenarios.
All tests passing (5578 tests, 0 failures).
Continues the pattern of using custom Ecto types to create rich domain objects
and avoid primitive obsession. Adds two new custom types following the
established pattern from IPAddress.
## MacAddress Custom Type
Provides structured MAC address handling with:
- Multiple input format support (colon, hyphen, dot-separated, compact)
- Normalization to colon-separated lowercase (canonical format)
- SNMP binary format conversion (6-byte binary)
- Format conversion helpers (:colon, :hyphen, :dot, :compact)
- Comprehensive validation and error handling
### Supported Formats
- Colon: `00:11:22:33:44:55`
- Hyphen: `00-11-22-33-44-55`
- Dot (Cisco): `0011.2233.4455`
- Compact: `001122334455`
- SNMP binary: `<<0, 17, 34, 51, 68, 85>>`
All formats normalize to lowercase colon-separated format for consistency.
## SnmpOid Custom Type
Provides structured SNMP OID handling with:
- OID format validation (numeric and named)
- Normalization to dotted-decimal with leading dot
- OID resolution via SnmpKit (named → numeric)
- Helper functions (parent, child_of?, append)
- Integer list representation support
### Supported Formats
- Dotted-decimal: `.1.3.6.1.2.1.1.1.0`
- Numeric (no leading dot): `1.3.6.1.2.1.1.1.0`
- Named OID: `sysDescr.0` (with automatic resolution)
- Integer list: `[1, 3, 6, 1, 2, 1, 1, 1, 0]`
## Benefits
- **Type Safety**: Structured data with validation at cast time
- **Normalization**: Consistent storage format regardless of input
- **Rich API**: Helper functions for common operations
- **Zero Migration Cost**: No database changes needed
- **Better Errors**: Clear validation errors instead of DB constraint violations
- **Domain Modeling**: Code works with %MacAddress{} and %SnmpOid{} instead of strings
## Database Storage
Both types store as VARCHAR in the database:
- MacAddress: VARCHAR(17) - colon-separated format
- SnmpOid: VARCHAR(255) - dotted-decimal format with leading dot
## Test Coverage
Added comprehensive test suites:
- MacAddress: 47 tests covering all formats, roundtrips, edge cases
- SnmpOid: 49 tests covering OID operations, validation, helpers
- All 96 tests passing ✅
## Usage Example
```elixir
# Before (primitive obsession)
field :mac_address, :string
field :sensor_oid, :string
# After (rich domain types)
field :mac_address, Towerops.EctoTypes.MacAddress
field :sensor_oid, Towerops.EctoTypes.SnmpOid
# Usage
changeset
|> cast(attrs, [:mac_address, :sensor_oid])
# Validation happens automatically in cast/1
```
Files:
- lib/towerops/ecto_types/mac_address.ex (330 lines)
- lib/towerops/ecto_types/snmp_oid.ex (340 lines)
- test/towerops/ecto_types/mac_address_test.exs (380 lines)
- test/towerops/ecto_types/snmp_oid_test.exs (390 lines)
Implements comprehensive validation layer on top of protobuf schema to
prevent malformed data, DoS attacks, and business logic violations.
Changes:
- Created Towerops.Agent.Validator module with strict validation for all
agent message types (AgentHeartbeat, MetricBatch, SnmpResult, AgentError,
CredentialTestResult, MikrotikResult)
- Added validation limits to prevent memory exhaustion and DoS attacks
(max string lengths, collection sizes, numeric ranges)
- Added format validation for UUIDs, IP addresses, versions, hostnames
- Added enum validation for status and protocol fields
- Modified AgentChannel handlers to validate all incoming messages before
processing
- Added comprehensive typespecs to AgentChannel for better static analysis
- Created safe_base64_decode/1 helper with error handling
- Added 32 comprehensive tests covering valid cases, invalid inputs, and
edge cases
Security improvements:
- Prevents memory exhaustion via oversized strings and collections
- Validates all numeric fields are within expected ranges
- Checks UUID format for all ID fields
- Validates IP addresses and hostnames against RFC standards
- Rejects future timestamps and excessive values
Backward compatibility:
- Empty version and hostname strings allowed for older agents
Improve coverage from 37.97% to 40.64% with tests for:
- PDU types in SNMPv3 messages (get_next, get_response, set, getbulk)
- Context engine ID and context name (empty, non-empty, both)
- Message max size variations (minimum 484, custom values)
- Multiple varbinds in v3 messages
- Error responses with various error_status values (0-5)
- Reportable flag variations (true/false)
- USM security model
- All SNMP value types (counter64, gauge32, ip_address, opaque, oid)
- Exception values (no_such_object, no_such_instance, end_of_mib_view)
- Message ID edge cases (0, max 2147483647)
- Discovery message consistency
Tests verify message encoding completes without crashes
even when V3 security processing is involved.
Coverage: 1,391 → 1,419 tests, all passing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added complete test coverage for MIB.Error module (16.13% -> target 100%):
**Error Creation:**
- All supported error types (syntax, semantic, import, type, constraint, etc.)
- Error creation with line and column information
- Error creation with context maps
- Suggestion generation for common mistakes
**Error Types Tested:**
- unexpected_token with various combinations (expected/actual/value)
- unexpected_eof with expected values
- unterminated_string
- invalid_number and invalid_identifier
- file_not_found with path
- import_error with symbol and module
- duplicate_definition with name
- Generic error types
**Suggestion Generation:**
- MAX-ACCESS vs ACCESS typo
- deprecated 'mandatory' vs 'current'
- OBJECT-TYPE vs OBJECT-IDENTITY confusion
- Unterminated string hints
- File not found with RFC/MIB name detection
- Import error dependency hints
**Error Formatting:**
- Format with/without line and column
- Format with/without suggestions
- Multiple suggestion formatting
- Edge cases (nil values, empty strings, non-standard types)
**Edge Cases:**
- All nil fields
- Large line numbers
- Zero/negative line/column values
- Various context data types
- Mixed token value types
- Empty options handling
53 new tests added
Test count: 1,211 -> 1,264 (all passing)
Added comprehensive test coverage for previously untested functionality:
- IP address parsing (valid IPs, invalid inputs, IPv6 rejection)
- Zero-filling behavior for incomplete IPs (Erlang :inet standard)
- Type inference for all SNMP types (integers, strings, lists, booleans, nulls)
- Value decoding for all SNMP types (strings, integers, IPs, OIDs, exceptions)
- Value encoding with type inference and validation controls
- Unsigned32 validation (valid ranges, overflow, underflow)
- Coercion edge cases (IP addresses, unsigned32, octet_string, OIDs, booleans)
Fixes:
- Corrected type inference expectation for large integers (returns :unsigned32)
- Fixed boolean assertion to use equality check instead of pattern match
- Documented :inet.parse_address zero-fill behavior for incomplete IPs
Total: 97 tests, all passing