404 lines
12 KiB
Markdown
404 lines
12 KiB
Markdown
# Attune Timer Sensor
|
|
|
|
A standalone sensor daemon for the Attune automation platform that monitors timer-based triggers and emits events. This sensor manages multiple concurrent timer schedules based on active rules.
|
|
|
|
## Overview
|
|
|
|
The timer sensor is a lightweight, event-driven process that:
|
|
|
|
- Listens for rule lifecycle events via RabbitMQ
|
|
- Manages per-rule timer tasks dynamically
|
|
- Emits events to the Attune API when timers fire
|
|
- Supports interval-based, cron-based, and datetime-based timers
|
|
- Authenticates using service account tokens
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Timer Sensor Process │
|
|
│ │
|
|
│ ┌────────────────┐ ┌──────────────────┐ │
|
|
│ │ Rule Lifecycle │───▶│ Timer Manager │ │
|
|
│ │ Listener │ │ │ │
|
|
│ │ (RabbitMQ) │ │ ┌──────────────┐ │ │
|
|
│ └────────────────┘ │ │ Rule 1 Timer │ │ │
|
|
│ │ ├──────────────┤ │ │
|
|
│ │ │ Rule 2 Timer │ │───┐ │
|
|
│ │ ├──────────────┤ │ │ │
|
|
│ │ │ Rule 3 Timer │ │ │ │
|
|
│ │ └──────────────┘ │ │ │
|
|
│ └──────────────────┘ │ │
|
|
│ │ │
|
|
│ ┌────────────────┐ │ │
|
|
│ │ API Client │◀──────────────────────────┘ │
|
|
│ │ (Create Events)│ │
|
|
│ └────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│ ▲
|
|
│ Events │ Rule Lifecycle
|
|
▼ │ Messages
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ Attune API │ │ RabbitMQ │
|
|
└─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
## Features
|
|
|
|
- **Per-Rule Timers**: Each rule gets its own independent timer task
|
|
- **Dynamic Management**: Timers start/stop automatically based on rule lifecycle
|
|
- **Multiple Timer Types**:
|
|
- **Interval**: Fire every N seconds/minutes/hours/days
|
|
- **Cron**: Fire based on cron expression (planned)
|
|
- **DateTime**: Fire at a specific date/time
|
|
- **Resilient**: Retries event creation with exponential backoff
|
|
- **Secure**: Token-based authentication with trigger type restrictions
|
|
- **Observable**: Structured JSON logging for monitoring
|
|
|
|
## Installation
|
|
|
|
### From Source
|
|
|
|
```bash
|
|
cargo build --release --package core-timer-sensor
|
|
sudo cp target/release/attune-core-timer-sensor /usr/local/bin/
|
|
```
|
|
|
|
### Using Cargo Install
|
|
|
|
```bash
|
|
cargo install --path crates/core-timer-sensor
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
The sensor requires the following environment variables:
|
|
|
|
| Variable | Required | Description | Example |
|
|
|----------|----------|-------------|---------|
|
|
| `ATTUNE_API_URL` | Yes | Base URL of the Attune API | `http://localhost:8080` |
|
|
| `ATTUNE_API_TOKEN` | Yes | Service account token | `eyJhbGci...` |
|
|
| `ATTUNE_SENSOR_REF` | Yes | Sensor reference (must be `core.timer`) | `core.timer` |
|
|
| `ATTUNE_MQ_URL` | Yes | RabbitMQ connection URL | `amqp://localhost:5672` |
|
|
| `ATTUNE_MQ_EXCHANGE` | No | RabbitMQ exchange name | `attune` (default) |
|
|
| `ATTUNE_LOG_LEVEL` | No | Logging verbosity | `info` (default) |
|
|
|
|
### Example: Environment Variables
|
|
|
|
```bash
|
|
export ATTUNE_API_URL="http://localhost:8080"
|
|
export ATTUNE_API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
export ATTUNE_SENSOR_REF="core.timer"
|
|
export ATTUNE_MQ_URL="amqp://localhost:5672"
|
|
export ATTUNE_LOG_LEVEL="info"
|
|
|
|
attune-core-timer-sensor
|
|
```
|
|
|
|
### Example: stdin Configuration
|
|
|
|
```bash
|
|
echo '{
|
|
"api_url": "http://localhost:8080",
|
|
"api_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"sensor_ref": "core.timer",
|
|
"mq_url": "amqp://localhost:5672",
|
|
"mq_exchange": "attune",
|
|
"log_level": "info"
|
|
}' | attune-core-timer-sensor --stdin-config
|
|
```
|
|
|
|
## Service Account Setup
|
|
|
|
Before running the sensor, you need to create a service account with the appropriate permissions:
|
|
|
|
```bash
|
|
# Create service account (requires admin token)
|
|
curl -X POST http://localhost:8080/service-accounts \
|
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "sensor:core.timer",
|
|
"scope": "sensor",
|
|
"description": "Timer sensor for interval-based triggers",
|
|
"ttl_hours": 72,
|
|
"metadata": {
|
|
"trigger_types": ["core.timer"]
|
|
}
|
|
}'
|
|
|
|
# Response will include the token (save this - it's only shown once!)
|
|
{
|
|
"identity_id": 123,
|
|
"name": "sensor:core.timer",
|
|
"scope": "sensor",
|
|
"token": "eyJhbGci...", # Use this as ATTUNE_API_TOKEN
|
|
"expires_at": "2025-01-30T12:34:56Z" # 72 hours from now
|
|
}
|
|
```
|
|
|
|
**Important**:
|
|
- The token is only displayed once. Store it securely!
|
|
- Sensor tokens expire after 24-72 hours and must be rotated
|
|
- Plan to rotate the token before expiration (set up monitoring/alerts)
|
|
|
|
## Timer Configuration
|
|
|
|
Rules using the `core.timer` trigger must provide configuration in `trigger_params`:
|
|
|
|
### Interval Timer
|
|
|
|
Fires every N units of time:
|
|
|
|
```json
|
|
{
|
|
"type": "interval",
|
|
"interval": 30,
|
|
"unit": "seconds" // "seconds", "minutes", "hours", "days"
|
|
}
|
|
```
|
|
|
|
Examples:
|
|
- Every 5 seconds: `{"type": "interval", "interval": 5, "unit": "seconds"}`
|
|
- Every 10 minutes: `{"type": "interval", "interval": 10, "unit": "minutes"}`
|
|
- Every 1 hour: `{"type": "interval", "interval": 1, "unit": "hours"}`
|
|
- Every 1 day: `{"type": "interval", "interval": 1, "unit": "days"}`
|
|
|
|
### DateTime Timer
|
|
|
|
Fires at a specific date/time (one-time):
|
|
|
|
```json
|
|
{
|
|
"type": "date_time",
|
|
"fire_at": "2025-01-27T15:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Cron Timer (Planned)
|
|
|
|
Fires based on cron expression:
|
|
|
|
```json
|
|
{
|
|
"type": "cron",
|
|
"expression": "0 0 * * *" // Daily at midnight
|
|
}
|
|
```
|
|
|
|
**Note**: Cron timers are not yet implemented.
|
|
|
|
## Running the Sensor
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Terminal 1: Start dependencies
|
|
docker-compose up -d postgres rabbitmq
|
|
|
|
# Terminal 2: Start API
|
|
cd crates/api
|
|
cargo run
|
|
|
|
# Terminal 3: Start sensor
|
|
export ATTUNE_API_URL="http://localhost:8080"
|
|
export ATTUNE_API_TOKEN="your_sensor_token_here"
|
|
export ATTUNE_SENSOR_REF="core.timer"
|
|
export ATTUNE_MQ_URL="amqp://localhost:5672"
|
|
|
|
cargo run --package core-timer-sensor
|
|
```
|
|
|
|
### Production (systemd)
|
|
|
|
Create a systemd service file at `/etc/systemd/system/attune-core-timer-sensor.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Attune Timer Sensor
|
|
After=network.target rabbitmq-server.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=attune
|
|
WorkingDirectory=/opt/attune
|
|
ExecStart=/usr/local/bin/attune-core-timer-sensor
|
|
Restart=always
|
|
RestartSec=10
|
|
|
|
# Environment variables
|
|
Environment="ATTUNE_API_URL=https://attune.example.com"
|
|
Environment="ATTUNE_SENSOR_REF=core.timer"
|
|
Environment="ATTUNE_MQ_URL=amqps://rabbitmq.example.com:5671"
|
|
Environment="ATTUNE_LOG_LEVEL=info"
|
|
|
|
# Load token from file
|
|
EnvironmentFile=/etc/attune/sensor-timer.env
|
|
|
|
# Security
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Create `/etc/attune/sensor-timer.env`:
|
|
|
|
```bash
|
|
ATTUNE_API_TOKEN=eyJhbGci...
|
|
```
|
|
|
|
Enable and start:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable attune-core-timer-sensor
|
|
sudo systemctl start attune-core-timer-sensor
|
|
sudo systemctl status attune-core-timer-sensor
|
|
```
|
|
|
|
**Token Rotation:**
|
|
|
|
Sensor tokens expire after 24-72 hours. To rotate:
|
|
|
|
```bash
|
|
# 1. Create new service account token (via API)
|
|
# 2. Update /etc/attune/sensor-timer.env with new token
|
|
sudo nano /etc/attune/sensor-timer.env
|
|
|
|
# 3. Restart sensor
|
|
sudo systemctl restart attune-core-timer-sensor
|
|
```
|
|
|
|
Set up a cron job or monitoring alert to remind you to rotate tokens every 72 hours.
|
|
|
|
View logs:
|
|
|
|
```bash
|
|
sudo journalctl -u attune-core-timer-sensor -f
|
|
```
|
|
|
|
## Monitoring
|
|
|
|
### Logs
|
|
|
|
The sensor outputs structured JSON logs:
|
|
|
|
```json
|
|
{
|
|
"timestamp": "2025-01-27T12:34:56Z",
|
|
"level": "info",
|
|
"message": "Timer fired for rule 123, created event 456",
|
|
"rule_id": 123,
|
|
"event_id": 456
|
|
}
|
|
```
|
|
|
|
### Health Checks
|
|
|
|
The sensor verifies API connectivity on startup. Monitor the logs for:
|
|
|
|
- `"API connectivity verified"` - Sensor connected successfully
|
|
- `"Timer started for rule"` - Timer activated for a rule
|
|
- `"Timer fired for rule"` - Event created by timer
|
|
- `"Failed to create event"` - Event creation error (check token/permissions)
|
|
|
|
## Troubleshooting
|
|
|
|
### "Invalid sensor_ref: expected 'core.timer'"
|
|
|
|
The `ATTUNE_SENSOR_REF` must be exactly `core.timer`. This sensor only handles timer triggers.
|
|
|
|
### "Failed to connect to Attune API"
|
|
|
|
- Verify `ATTUNE_API_URL` is correct and reachable
|
|
- Check that the API service is running
|
|
- Ensure no firewall blocking the connection
|
|
|
|
### "Insufficient permissions to create event for trigger type 'core.timer'"
|
|
|
|
The service account token doesn't have permission to create timer events. Ensure the token's metadata includes `"trigger_types": ["core.timer"]`.
|
|
|
|
### "Failed to connect to RabbitMQ"
|
|
|
|
- Verify `ATTUNE_MQ_URL` is correct
|
|
- Check that RabbitMQ is running
|
|
- Ensure credentials are correct in the URL
|
|
|
|
### "Token expired"
|
|
|
|
The service account token has exceeded its TTL (24-72 hours). This is expected behavior.
|
|
|
|
**Solution:**
|
|
1. Create a new service account token via API
|
|
2. Update `ATTUNE_API_TOKEN` environment variable
|
|
3. Restart the sensor
|
|
|
|
**Prevention:**
|
|
- Set up monitoring to alert 6 hours before token expiration
|
|
- Plan regular token rotation (every 72 hours maximum)
|
|
|
|
### Timer not firing
|
|
|
|
1. Check that the rule is enabled
|
|
2. Verify the rule's `trigger_type` is `core.timer`
|
|
3. Check the sensor logs for "Timer started for rule"
|
|
4. Ensure `trigger_params` is valid JSON matching the timer config format
|
|
|
|
## Development
|
|
|
|
### Running Tests
|
|
|
|
```bash
|
|
cargo test --package core-timer-sensor
|
|
```
|
|
|
|
### Building
|
|
|
|
```bash
|
|
# Debug build
|
|
cargo build --package core-timer-sensor
|
|
|
|
# Release build
|
|
cargo build --release --package core-timer-sensor
|
|
```
|
|
|
|
### Code Structure
|
|
|
|
```
|
|
crates/core-timer-sensor/
|
|
├── src/
|
|
│ ├── main.rs # Entry point, initialization
|
|
│ ├── config.rs # Configuration loading (env/stdin)
|
|
│ ├── api_client.rs # Attune API communication
|
|
│ ├── timer_manager.rs # Per-rule timer task management
|
|
│ ├── rule_listener.rs # RabbitMQ message consumer
|
|
│ └── types.rs # Shared types and enums
|
|
├── Cargo.toml
|
|
└── README.md
|
|
```
|
|
|
|
## Contributing
|
|
|
|
When adding new timer types:
|
|
|
|
1. Add variant to `TimerConfig` enum in `types.rs`
|
|
2. Implement spawn logic in `timer_manager.rs`
|
|
3. Add tests for the new timer type
|
|
4. Update this README with examples
|
|
|
|
## License
|
|
|
|
MIT License - see LICENSE file for details.
|
|
|
|
## See Also
|
|
|
|
- [Sensor Interface Specification](../../docs/sensor-interface.md)
|
|
- [Service Accounts Documentation](../../docs/service-accounts.md)
|
|
- [Sensor Authentication Overview](../../docs/sensor-authentication-overview.md)
|