towerops-agent/CLAUDE.md

365 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Towerops Agent - Development Notes
This file provides context for Claude Code when working on the Rust agent.
## Project Overview
Lightweight Rust agent for remote SNMP polling. Deployed on customer networks to poll local SNMP devices and report metrics to Towerops API via HTTPS.
## What's Complete ✅
### Architecture & Design
- [x] Complete module structure (13 source files)
- [x] Configuration types matching Phoenix API responses
- [x] Metric types (SensorReading, InterfaceStat, NeighborDiscovery) with proper serialization
- [x] Event loop with 5 concurrent tasks (tokio::select!)
- [x] SQLite buffering with 24-hour retention
- [x] Error types and result handling throughout
### Core Functionality
- [x] **API Client** (`api_client.rs`)
- fetch_config() - GET /api/v1/agent/config (Protocol Buffers)
- submit_metrics() - POST /api/v1/agent/metrics (Protocol Buffers)
- heartbeat() - POST /api/v1/agent/heartbeat (Protocol Buffers)
- Uses ureq with rustls-tls (30s timeout)
- Full Protocol Buffers integration for all endpoints
- [x] **Storage** (`buffer/storage.rs`)
- store_metric() - Save metrics to SQLite
- get_pending_metrics() - Retrieve unsent metrics
- mark_metrics_sent() - Track submission
- cleanup_old_metrics() - Remove old data
- Last poll time tracking per equipment
- [x] **Scheduler** (`poller/scheduler.rs`)
- Config refresh every 5 minutes
- Metrics flush every 30 seconds
- Heartbeat every 60 seconds
- Cleanup every hour
- Poll check every 5 seconds
- Update check and auto-update every hour
- [x] **Executor** (`poller/executor.rs`)
- poll_sensors() - Poll configured sensors
- poll_interfaces() - Poll interface statistics
- poll_neighbors() - Poll LLDP and CDP neighbors
- Parallel polling with tokio::join!
- Applies sensor divisors
- [x] **Neighbor Discovery** (`snmp/neighbor.rs`)
- discover_neighbors() - Main discovery function
- discover_lldp_neighbors() - LLDP-MIB (IEEE 802.1AB)
- discover_cdp_neighbors() - CISCO-CDP-MIB
- Parses remote device information and capabilities
- Associates neighbors with local interfaces
- [x] **Main** (`main.rs`)
- CLI with clap (--api-url, --token, etc.)
- Environment variable support
- Logging with tracing
- Graceful startup/shutdown
- Docker image version checking on startup
- [x] **Version Checking & Auto-Update** (`version.rs`)
- Checks Docker Hub for newer image versions on startup
- Performs periodic checks every hour (configurable in scheduler)
- Automatically pulls new image and restarts when update available
- Compares current version with latest available
- Logs warnings if updates are available
- Non-blocking, fails gracefully if Docker Hub unavailable
- Requires Docker socket mount (`/var/run/docker.sock`) for self-update
### Protocol Buffers Integration
- [x] **Protobuf Definitions** (`proto/agent.proto`)
- AgentConfig, Equipment, SnmpConfig, Sensor, Interface
- MetricBatch, Metric, SensorReading, InterfaceStat, NeighborDiscovery
- HeartbeatMetadata, HeartbeatResponse
- [x] **Code Generation** (`build.rs`)
- Uses prost-build to compile protobuf definitions
- Generates Rust types at build time
- [x] **API Communication**
- Config endpoint: Accepts `application/x-protobuf`, decodes response
- Metrics endpoint: Encodes batch to protobuf, sends with proper content-type
- Heartbeat endpoint: Encodes metadata to protobuf
- Conversion functions between protobuf and internal types
### Build & Deployment
- [x] Cargo.toml with optimized release profile
- opt-level = "z"
- lto = true
- codegen-units = 1
- strip = true
- [x] Multi-stage Dockerfile (Alpine, ~10-20 MB)
- [x] docker-compose.example.yml
- [x] README with user documentation
- [x] .gitignore and .dockerignore
- [x] GitLab CI/CD configured for Docker Hub
### Build Status
```bash
✅ cargo build --release - SUCCESS
✅ cargo clippy - 0 warnings, 0 errors
📦 Target size optimized for minimal footprint
🚀 Protobuf integration complete
```
## Testing Gaps
- [ ] Unit tests for SNMP client
- [ ] Unit tests for storage (SQLite operations)
- [ ] Unit tests for API client (mock server)
- [ ] Integration test with real SNMP device
## Development Workflow
### Quick Start
1. **Build the agent**:
```bash
cargo build --release
```
2. **Run locally** (needs Phoenix backend running):
```bash
cargo run -- \
--api-url http://localhost:4000 \
--token <get-from-ui> \
--database-path ./test.db
```
3. **Watch logs**:
```bash
RUST_LOG=debug cargo run -- ...
```
### Testing Changes
1. **Check compilation**:
```bash
cargo check
```
2. **Run tests**:
```bash
cargo test
```
3. **Format code**:
```bash
cargo fmt
```
4. **Check for issues**:
```bash
cargo clippy
```
### Docker Testing
1. **Build image**:
```bash
docker build -t towerops-agent:test .
```
2. **Run container**:
```bash
docker run --rm \
-e TOWEROPS_API_URL=http://host.docker.internal:4000 \
-e TOWEROPS_AGENT_TOKEN=<token> \
-e RUST_LOG=info \
-v $(pwd)/data:/data \
towerops-agent:test
```
### CI/CD Pipeline
**Automated builds** via GitLab CI (`.gitlab-ci.yml`):
- Push to branch → test + build with branch tag
- Push to main → test + build + tag as `latest`
- Create tag (e.g., `v0.1.0`) → test + build + release
**Registry**: `registry.gitlab.com/towerops/towerops-agent`
**See**: `DEPLOYMENT.md` for complete CI/CD documentation
## Integration with Phoenix Backend
### API Endpoints (from agent perspective)
**GET /api/v1/agent/config**
- Headers: `Authorization: Bearer <token>`
- Response: Equipment list with sensors and interfaces
- Called every 5 minutes
**POST /api/v1/agent/metrics**
- Headers: `Authorization: Bearer <token>`
- Body: `{"metrics": [...]}`
- Response: `{"status": "accepted", "received": N}`
- Called every 30 seconds with pending metrics
**POST /api/v1/agent/heartbeat**
- Headers: `Authorization: Bearer <token>`
- Body: `{"version": "0.1.0", "hostname": "...", "uptime_seconds": 3600}`
- Response: `{"status": "ok"}`
- Called every 60 seconds
### Getting a Test Token
1. Start Phoenix: `mix phx.server`
2. Navigate to: `http://localhost:4000/orgs/:slug/agents`
3. Click "Create New Agent"
4. Copy the token (shown only once)
5. Use in agent: `--token <copied-token>`
## Architecture Decisions
### Why Tokio?
- Async event loop for efficient I/O
- Multiple concurrent timers (config, metrics, heartbeat)
- Non-blocking SNMP operations via spawn_blocking
### Why SQLite?
- Embedded, no external dependencies
- Persist metrics during API outages
- Small footprint (~100 MB for 24h of metrics)
- No configuration needed
### Why Rust?
- Small binary size (~10-20 MB with Alpine)
- Low memory usage (<256 MB typical)
- Cross-compile to multiple architectures
- Strong type safety for reliability
### Why Async SNMP with spawn_blocking?
- SNMP crate uses synchronous I/O
- spawn_blocking moves sync operations to thread pool
- Keeps main event loop non-blocking
- Allows concurrent polling without blocking other tasks
## Common Issues
### "Failed to fetch config" Error
**Check**:
1. Is Phoenix backend running?
2. Is the token valid (not revoked)?
3. Is the API URL correct?
4. Is there network connectivity?
### High Memory Usage
**Check**:
1. Database size: `ls -lh /data/towerops-agent.db`
2. Are metrics being submitted? (check logs)
3. Is cleanup running? (should see log every hour)
### Agent Not Showing as Online
**Check**:
1. Is heartbeat working? (check Phoenix logs)
2. Check `last_seen_at` in database: `SELECT last_seen_at FROM agent_tokens WHERE token_hash = ...`
3. Time sync between agent and server
## File Organization
```
towerops-agent/
├── src/
│ ├── main.rs # Entry point, CLI, initialization
│ ├── config.rs # Types matching API responses
│ ├── api_client.rs # HTTP client for Towerops API
│ ├── version.rs # Docker image version checking
│ ├── metrics/
│ │ └── mod.rs # Metric types (SensorReading, InterfaceStat, NeighborDiscovery)
│ ├── snmp/
│ │ ├── mod.rs # Module exports
│ │ ├── client.rs # ✅ SNMP client (GET and WALK)
│ │ ├── neighbor.rs # ✅ LLDP and CDP neighbor discovery
│ │ └── types.rs # SNMP types and errors
│ ├── buffer/
│ │ ├── mod.rs # Module exports
│ │ └── storage.rs # SQLite buffering
│ └── poller/
│ ├── mod.rs # Module exports
│ ├── executor.rs # Poll execution logic
│ └── scheduler.rs # Main event loop
├── Cargo.toml # Dependencies and build config
├── Dockerfile # Multi-stage build
├── README.md # User documentation
└── CLAUDE.md # This file
```
## Dependencies
**Key Crates**:
- `tokio` - Async runtime with full features
- `reqwest` - HTTP client (rustls-tls, no default features)
- `rusqlite` - SQLite (bundled)
- `serde` + `serde_json` - Serialization
- `snmp` - SNMP operations (v0.2) needs integration
- `tracing` + `tracing-subscriber` - Logging
- `clap` - CLI argument parsing
- `chrono` - Timestamps
- `anyhow` + `thiserror` - Error handling
- `hostname` - Get system hostname
## Next Actions
**Immediate** (for production readiness):
1. Add more comprehensive unit tests
2. Integration test with mock SNMP device
3. Load test with 100 devices
4. Stability test (7+ days continuous)
**Long-term** (nice to have):
1. SNMPv3 support
2. Agent-side threshold filtering
3. Configurable sampling rates
4. Agent health metrics endpoint
## Resources
- **Main Implementation Doc**: `/Users/graham/dev/towerops/AGENT_IMPLEMENTATION.md`
- **Next Steps Guide**: `/Users/graham/dev/towerops/AGENT_NEXT_STEPS.md`
- **SNMP Crate Docs**: https://docs.rs/snmp/0.2.2/snmp/
- **SNMP Crate Source**: https://github.com/hroi/snmp-rs
## Success Criteria
Agent is production-ready when:
- [x] Compiles successfully
- [x] Docker image builds
- [x] API client works (config, metrics, heartbeat)
- [x] SQLite buffering works
- [x] Event loop runs without panics
- [x] **SNMP polling works**
- [x] **Neighbor discovery works (LLDP/CDP)**
- [ ] **Integration testing complete** CURRENT FOCUS
- [ ] Metrics appear in Phoenix database
- [ ] Neighbor data appears in Phoenix database
- [ ] Survives 24h API outage
- [ ] Uses <256 MB memory with 50 devices
- [ ] Runs for 7+ days without issues
## Notes for Future Development
### Adding New Metric Types
1. Add variant to `Metric` enum in `src/metrics/mod.rs`
2. Update `metric_type()` and `timestamp()` methods
3. Update Phoenix API to accept new type
4. Add serialization test
### Adding New Configuration Fields
1. Update structs in `src/config.rs`
2. Update Phoenix API `build_equipment_config/1`
3. Consider backwards compatibility
### Debugging SNMP Issues
- Set `RUST_LOG=debug` to see all SNMP operations
- Check IP reachability: `ping <device-ip>`
- Test SNMP manually: `snmpget -v2c -c public <device-ip> <oid>`
- Verify community string is correct
- Check firewall rules (UDP port 161)
---
**Last Updated**: January 14, 2026
**Status**: All code complete, integration testing needed
**Version**: 0.1.0 (pre-release)