docs(scheduler): add comprehensive README and CHANGELOG
Add complete documentation for The Scheduler service. README.md (500+ lines): - Architecture overview with ASCII diagram - Quick start guide - Complete API reference with curl examples - Task scheduling patterns and examples - Priority system documentation - Built-in executors documentation (example, doc_sync, config_backup) - Custom executor development guide - Current tasks table - Database schema documentation - Testing guide with coverage metrics - Development and debugging information - Monitoring and troubleshooting - Security and performance notes - API reference with response codes and filtering CHANGELOG.md: - Initial v1.0.0 release documentation - Core features and architecture - REST API endpoints - Task executors and pre-configured tasks - Testing infrastructure and metrics - Technical details and dependencies - Coverage metrics breakdown - Planned features for future releases Documentation covers: - All API endpoints and authentication - Scheduling examples (every minute, daily, monthly, etc.) - Priority ranges and usage - Executor configuration - Test database setup - Docker stack configuration - Common issues and solutions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to The Scheduler will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-12-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### Core Scheduling System
|
||||||
|
- **Hybrid APScheduler + PostgreSQL architecture** for minute-based task scheduling
|
||||||
|
- Single scheduler job runs every minute
|
||||||
|
- Queries database for tasks scheduled for current minute
|
||||||
|
- Executes up to 5 tasks concurrently by priority
|
||||||
|
- Priority queue system (1-100, lower = higher priority)
|
||||||
|
- **Cron-like scheduling** with wildcard support (`-1` = any)
|
||||||
|
- Supports minute, hour, day_of_month, month, day_of_week patterns
|
||||||
|
- Flexible scheduling from every-minute to specific dates
|
||||||
|
- **Task execution tracking** with full audit trail
|
||||||
|
- Database tables: `scheduled_tasks` (definitions) and `task_executions` (history)
|
||||||
|
- Tracks status, duration, output, errors, and retry attempts
|
||||||
|
- Execution metadata stored as JSONB
|
||||||
|
|
||||||
|
#### REST API
|
||||||
|
- **Full CRUD API** for task management with FastAPI
|
||||||
|
- `POST /tasks` - Create new scheduled task
|
||||||
|
- `GET /tasks` - List all tasks with filtering (service, enabled)
|
||||||
|
- `GET /tasks/{name}` - Get task details
|
||||||
|
- `PUT /tasks/{name}` - Update task configuration
|
||||||
|
- `DELETE /tasks/{name}` - Remove task
|
||||||
|
- `POST /tasks/{name}/trigger` - Manually trigger task execution
|
||||||
|
- **Execution history endpoints**
|
||||||
|
- `GET /executions` - Query execution history
|
||||||
|
- Filter by task_name, status, service
|
||||||
|
- Pagination support (limit parameter)
|
||||||
|
- **System monitoring endpoints**
|
||||||
|
- `GET /health` - Health check
|
||||||
|
- `GET /stats` - System statistics (enabled tasks, running tasks, 24h execution counts)
|
||||||
|
- **API Key authentication** (Bearer token) for all protected endpoints
|
||||||
|
- **OpenAPI documentation** at `/docs`
|
||||||
|
|
||||||
|
#### Task Executors
|
||||||
|
- **Example Executor** (`example_executor.py`)
|
||||||
|
- Simple test implementation with configurable message and delay
|
||||||
|
- Demonstrates executor pattern
|
||||||
|
- **Documentation Sync Executor** (`doc_sync_executor.py`)
|
||||||
|
- Mirrors documentation from upstream Git repositories to Gitea
|
||||||
|
- Supports full repository mirroring or selective path syncing
|
||||||
|
- Creates date-tagged snapshots (YYYY-MM-DD format)
|
||||||
|
- Generates `.SYNC_INFO.md` with sync metadata
|
||||||
|
- Configurable upstream repo, paths, branch, and Gitea destination
|
||||||
|
- **Config Backup Executor** (`config_backup_executor.py`)
|
||||||
|
- Backs up Docker configurations and data directories
|
||||||
|
- Supports multiple source paths with exclusion patterns
|
||||||
|
- Optional compression (tar.gz)
|
||||||
|
- Retention policy (days-based cleanup)
|
||||||
|
- Creates timestamped backups
|
||||||
|
|
||||||
|
#### Pre-configured Tasks
|
||||||
|
- **FastAPI Documentation Sync** (monthly on 11th at 04:00)
|
||||||
|
- Syncs entire FastAPI repository to `library/docs-fastapi`
|
||||||
|
- Priority: 60 (maintenance)
|
||||||
|
- **Ollama Documentation Sync** (monthly on 12th at 04:00)
|
||||||
|
- Syncs only `/docs` folder from Ollama repository to `library/docs-ollama`
|
||||||
|
- Priority: 60 (maintenance)
|
||||||
|
- **Docker Config Backup** (daily at 03:05)
|
||||||
|
- Backs up Docker data and configurations
|
||||||
|
- Priority: 20 (user task)
|
||||||
|
- 30-day retention
|
||||||
|
- **Example Test Task** (every minute, can be disabled)
|
||||||
|
- Test task for validation
|
||||||
|
- Priority: 50 (maintenance)
|
||||||
|
|
||||||
|
#### Testing Infrastructure
|
||||||
|
- **Comprehensive test suite** with 80% code coverage
|
||||||
|
- 85 total tests across multiple test files
|
||||||
|
- Pytest configuration with markers (unit, integration, api, executor)
|
||||||
|
- Coverage reporting with pytest-cov
|
||||||
|
- **Test categories**:
|
||||||
|
- Unit tests: Fast tests with mocked dependencies
|
||||||
|
- API tests: Comprehensive endpoint testing (24 tests)
|
||||||
|
- Executor tests: Task executor validation
|
||||||
|
- Integration tests: Real database operations
|
||||||
|
- **Test database setup**
|
||||||
|
- Dedicated `test_scheduler` database on postgres-shared
|
||||||
|
- Automatic schema creation and cleanup
|
||||||
|
- Database fixtures for clean test state
|
||||||
|
- Test user: `test_scheduler_user`
|
||||||
|
- **Test fixtures** (conftest.py)
|
||||||
|
- Mock database connections
|
||||||
|
- Mock scheduler and executor
|
||||||
|
- Sample task data
|
||||||
|
- Authentication headers
|
||||||
|
- Clean database state management
|
||||||
|
|
||||||
|
#### Documentation
|
||||||
|
- **Comprehensive README.md** (500+ lines)
|
||||||
|
- Architecture overview with ASCII diagram
|
||||||
|
- Quick start guide
|
||||||
|
- Complete API reference with curl examples
|
||||||
|
- Task scheduling patterns and examples
|
||||||
|
- Executor development guide
|
||||||
|
- Testing guide with coverage metrics
|
||||||
|
- Development and debugging information
|
||||||
|
- Security and performance notes
|
||||||
|
- **Test database setup guide** (test_database_setup.sql)
|
||||||
|
- SQL script for creating test environment
|
||||||
|
- Schema matching production
|
||||||
|
- Test data fixtures
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
- **Pydantic Settings** for environment-based configuration
|
||||||
|
- PostgreSQL connection settings
|
||||||
|
- Redis connection (for future use)
|
||||||
|
- Gitea authentication
|
||||||
|
- API key configuration
|
||||||
|
- Computed properties (database_url, redis_url)
|
||||||
|
- **Docker stack configuration** (stacks/scheduler.yml)
|
||||||
|
- Virtual environment setup on startup
|
||||||
|
- Git configuration for librarian user
|
||||||
|
- Health checks
|
||||||
|
- Network isolation (docker-dataplane)
|
||||||
|
- Resource limits
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
#### Database Schema
|
||||||
|
```sql
|
||||||
|
scheduled_tasks:
|
||||||
|
- Task definitions/templates
|
||||||
|
- Scheduling configuration (minute/hour/day patterns)
|
||||||
|
- Priority, enabled status, retry settings
|
||||||
|
- Task configuration as JSONB
|
||||||
|
- Execution tracking fields
|
||||||
|
|
||||||
|
task_executions:
|
||||||
|
- Individual execution records
|
||||||
|
- Status tracking (pending, running, success, failed, timeout)
|
||||||
|
- Duration and timestamp tracking
|
||||||
|
- Output and error details
|
||||||
|
- Metadata as JSONB
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Performance
|
||||||
|
- Minute-based processing with lightweight scheduler ticks
|
||||||
|
- Connection pooling for database efficiency
|
||||||
|
- Database indexes for optimized task queries
|
||||||
|
- Concurrent execution with configurable limit (default: 5)
|
||||||
|
- Priority-based execution order
|
||||||
|
|
||||||
|
#### Security
|
||||||
|
- API key authentication required for protected endpoints
|
||||||
|
- Network isolation on docker-dataplane
|
||||||
|
- Environment variable-based secrets
|
||||||
|
- Gitea token authentication for git operations
|
||||||
|
- Database credentials in environment
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- FastAPI (web framework)
|
||||||
|
- APScheduler (task scheduling)
|
||||||
|
- psycopg2-binary (PostgreSQL driver)
|
||||||
|
- Pydantic (configuration management)
|
||||||
|
- pytest + pytest-asyncio + pytest-cov (testing)
|
||||||
|
- GitPython (git operations)
|
||||||
|
|
||||||
|
### Coverage Metrics
|
||||||
|
```
|
||||||
|
Module Coverage
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
config.py 100% ✅
|
||||||
|
example_executor.py 100% ✅
|
||||||
|
main.py (API endpoints) 95% ✅
|
||||||
|
doc_sync_executor.py 78% ✅
|
||||||
|
executor.py (core logic) 71% ✅
|
||||||
|
config_backup_executor.py 50% 📈
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
TOTAL 80% 🎯
|
||||||
|
```
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Planned
|
||||||
|
- Redis integration for distributed locking
|
||||||
|
- Webhook notifications for task completion
|
||||||
|
- Task dependencies (run task B after task A succeeds)
|
||||||
|
- Task groups and tags
|
||||||
|
- More executors (database backup, log rotation, etc.)
|
||||||
|
- Web UI for task management
|
||||||
|
- Metrics export (Prometheus)
|
||||||
|
- Advanced scheduling (last business day of month, etc.)
|
||||||
@@ -0,0 +1,576 @@
|
|||||||
|
# The Scheduler
|
||||||
|
|
||||||
|
**System-wide maintenance orchestration for automated task scheduling.**
|
||||||
|
|
||||||
|
The Scheduler is a hybrid APScheduler + PostgreSQL-based task scheduling system that handles backups, documentation mirroring, cleanup, and automated maintenance tasks across the Portainer Core infrastructure.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Hybrid Design**: APScheduler + Database-driven Priority System
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ APScheduler (runs every minute) │
|
||||||
|
│ └─> Query PostgreSQL for tasks scheduled this minute │
|
||||||
|
│ └─> Execute up to 5 tasks concurrently by priority │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- **Minute-based scheduling** with cron-like patterns (`-1` = wildcard)
|
||||||
|
- **Priority queue** (1-100, lower = higher priority)
|
||||||
|
- **Concurrent execution** (max 5 tasks simultaneously)
|
||||||
|
- **Execution tracking** (full audit trail in database)
|
||||||
|
- **REST API** for task management
|
||||||
|
- **Flexible executors** (modular task implementations)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Starting the Scheduler
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy via Portainer
|
||||||
|
# Uses stack: /stacks/scheduler.yml
|
||||||
|
|
||||||
|
# Check health
|
||||||
|
curl http://localhost:8090/health
|
||||||
|
|
||||||
|
# View API docs
|
||||||
|
open http://localhost:8090/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Authentication
|
||||||
|
|
||||||
|
All protected endpoints require an API key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set in environment or use default
|
||||||
|
export SCHEDULER_API_KEY="your-api-key-here"
|
||||||
|
|
||||||
|
# Make authenticated request
|
||||||
|
curl -H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
http://localhost:8090/tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scheduling Tasks
|
||||||
|
|
||||||
|
### Task Configuration
|
||||||
|
|
||||||
|
Tasks are defined with:
|
||||||
|
- **task_name**: Unique identifier
|
||||||
|
- **service**: Which service owns this task
|
||||||
|
- **executor**: Python module to execute
|
||||||
|
- **priority**: 1-100 (1=emergency, 10-30=user, 40-70=maintenance)
|
||||||
|
- **schedule**: Minute, hour, day, month, day_of_week (-1 = any)
|
||||||
|
- **config**: JSON configuration for executor
|
||||||
|
|
||||||
|
### Scheduling Examples
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Every minute (wildcard)
|
||||||
|
minute=-1, hour=-1, day_of_month=-1, month=-1, day_of_week=-1
|
||||||
|
|
||||||
|
# Daily at 3:05 AM
|
||||||
|
minute=5, hour=3, day_of_month=-1, month=-1, day_of_week=-1
|
||||||
|
|
||||||
|
# 11th of every month at 4:00 AM
|
||||||
|
minute=0, hour=4, day_of_month=11, month=-1, day_of_week=-1
|
||||||
|
|
||||||
|
# Every Monday at 9:00 AM
|
||||||
|
minute=0, hour=9, day_of_month=-1, month=-1, day_of_week=0
|
||||||
|
|
||||||
|
# First day of January at midnight
|
||||||
|
minute=0, hour=0, day_of_month=1, month=1, day_of_week=-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority System
|
||||||
|
|
||||||
|
```
|
||||||
|
Priority Range Purpose Examples
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
1-5 Emergency/System Critical system tasks
|
||||||
|
10-30 User Tasks User-initiated operations
|
||||||
|
40-70 Maintenance Backups, cleanup, doc sync
|
||||||
|
71-100 Low Priority Optional background tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
### Task Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all tasks
|
||||||
|
GET /tasks
|
||||||
|
GET /tasks?enabled=true&service=scheduler
|
||||||
|
|
||||||
|
# Get task details
|
||||||
|
GET /tasks/{task_name}
|
||||||
|
|
||||||
|
# Create new task
|
||||||
|
POST /tasks
|
||||||
|
{
|
||||||
|
"task_name": "my_task",
|
||||||
|
"service": "scheduler",
|
||||||
|
"executor": "example_executor",
|
||||||
|
"priority": 50,
|
||||||
|
"minute": 0,
|
||||||
|
"hour": 4,
|
||||||
|
"description": "Daily task at 4 AM",
|
||||||
|
"config": {"key": "value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update task
|
||||||
|
PUT /tasks/{task_name}
|
||||||
|
{
|
||||||
|
"priority": 60,
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete task
|
||||||
|
DELETE /tasks/{task_name}
|
||||||
|
|
||||||
|
# Manually trigger task
|
||||||
|
POST /tasks/{task_name}/trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution History
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View recent executions
|
||||||
|
GET /executions?limit=20
|
||||||
|
|
||||||
|
# Filter by task
|
||||||
|
GET /executions?task_name=my_task&limit=10
|
||||||
|
|
||||||
|
# Filter by status
|
||||||
|
GET /executions?status=success
|
||||||
|
|
||||||
|
# Filter by service
|
||||||
|
GET /executions?service=scheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Stats
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get system statistics
|
||||||
|
GET /stats
|
||||||
|
{
|
||||||
|
"scheduler_running": true,
|
||||||
|
"tasks_enabled": 3,
|
||||||
|
"tasks_currently_running": 0,
|
||||||
|
"concurrent_limit": 5,
|
||||||
|
"execution_stats_24h": {
|
||||||
|
"success": 15,
|
||||||
|
"failed": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task Executors
|
||||||
|
|
||||||
|
Executors are Python modules that implement the actual task logic.
|
||||||
|
|
||||||
|
### Built-in Executors
|
||||||
|
|
||||||
|
#### 1. Example Executor
|
||||||
|
**File**: `src/executors/example_executor.py`
|
||||||
|
**Purpose**: Test/example implementation
|
||||||
|
|
||||||
|
```python
|
||||||
|
config = {
|
||||||
|
"message": "Hello World",
|
||||||
|
"delay_seconds": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Doc Sync Executor
|
||||||
|
**File**: `src/executors/doc_sync_executor.py`
|
||||||
|
**Purpose**: Mirror documentation from GitHub to Gitea
|
||||||
|
|
||||||
|
```python
|
||||||
|
config = {
|
||||||
|
"project": "fastapi",
|
||||||
|
"upstream_repo": "https://github.com/tiangolo/fastapi.git",
|
||||||
|
"docs_paths": ["/docs"], # Empty = entire repo
|
||||||
|
"gitea_repo": "library/docs-fastapi",
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Clones upstream repository
|
||||||
|
- Filters to specific paths or mirrors entire repo
|
||||||
|
- Pushes to Gitea with authentication
|
||||||
|
- Creates date-tagged snapshots
|
||||||
|
- Generates `.SYNC_INFO.md` with metadata
|
||||||
|
|
||||||
|
#### 3. Config Backup Executor
|
||||||
|
**File**: `src/executors/config_backup_executor.py`
|
||||||
|
**Purpose**: Backup Docker configs and data
|
||||||
|
|
||||||
|
```python
|
||||||
|
config = {
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"path": "/data/docker-data",
|
||||||
|
"name": "docker-data",
|
||||||
|
"excludes": ["*/cache/*", "*.log"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"backup_dir": "/backups/configs",
|
||||||
|
"compress": true,
|
||||||
|
"retention_days": 30
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Custom Executors
|
||||||
|
|
||||||
|
1. Create file in `src/executors/your_executor.py`
|
||||||
|
2. Implement async `execute(config: dict, settings: Settings) -> str` function
|
||||||
|
3. Return success message or raise exception on failure
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/executors/my_executor.py
|
||||||
|
async def execute(config: dict, settings: Settings) -> str:
|
||||||
|
"""
|
||||||
|
Execute custom task.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Task configuration from database
|
||||||
|
settings: Global scheduler settings
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success message
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: On task failure
|
||||||
|
"""
|
||||||
|
# Your task logic here
|
||||||
|
result = do_something(config.get('param'))
|
||||||
|
|
||||||
|
return f"Task completed: {result}"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create task using API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8090/tasks \
|
||||||
|
-H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"task_name": "my_custom_task",
|
||||||
|
"service": "scheduler",
|
||||||
|
"executor": "my_executor",
|
||||||
|
"priority": 50,
|
||||||
|
"minute": 0,
|
||||||
|
"hour": 2,
|
||||||
|
"config": {"param": "value"}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Tasks
|
||||||
|
|
||||||
|
### Active Schedules
|
||||||
|
|
||||||
|
| Task | Priority | Schedule | Description |
|
||||||
|
|------|----------|----------|-------------|
|
||||||
|
| `test_example_task` | 50 | Every minute | Test task (can be disabled) |
|
||||||
|
| `backup_docker_configs_daily` | 20 | Daily 03:05 | Backup Docker configs |
|
||||||
|
| `sync_fastapi_docs_monthly` | 60 | 11th @ 04:00 | Sync FastAPI docs to Gitea |
|
||||||
|
| `sync_ollama_docs_monthly` | 60 | 12th @ 04:00 | Sync Ollama docs to Gitea |
|
||||||
|
|
||||||
|
### Managing Tasks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable test task
|
||||||
|
curl -X PUT http://localhost:8090/tasks/test_example_task \
|
||||||
|
-H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"enabled": false}'
|
||||||
|
|
||||||
|
# View execution history
|
||||||
|
curl -H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
http://localhost:8090/executions?limit=10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
### scheduled_tasks
|
||||||
|
|
||||||
|
Stores task definitions (templates for execution).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
- id (serial)
|
||||||
|
- task_name (varchar, unique)
|
||||||
|
- service (varchar) - owner service
|
||||||
|
- executor (varchar) - executor module name
|
||||||
|
- priority (integer) - 1-100
|
||||||
|
- minute, hour, day_of_month, month, day_of_week (integer) - schedule
|
||||||
|
- enabled (boolean)
|
||||||
|
- description (text)
|
||||||
|
- config (jsonb) - executor configuration
|
||||||
|
- last_run, last_status, last_duration_seconds - tracking
|
||||||
|
- retry_count, max_retries, timeout_seconds - execution control
|
||||||
|
- created_at, updated_at, created_by - metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
### task_executions
|
||||||
|
|
||||||
|
Stores individual execution records (audit trail).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
- id (serial)
|
||||||
|
- task_id (integer) - references scheduled_tasks
|
||||||
|
- task_name, service, executor, priority - snapshot
|
||||||
|
- status (varchar) - pending, running, success, failed, timeout
|
||||||
|
- triggered_by (varchar) - scheduler, manual, retry
|
||||||
|
- triggered_at, started_at, completed_at - timestamps
|
||||||
|
- duration_seconds (integer)
|
||||||
|
- output (text) - success message
|
||||||
|
- error (text) - error details
|
||||||
|
- retry_count (integer)
|
||||||
|
- metadata (jsonb)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All tests with coverage
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest --cov=src --cov-report=term
|
||||||
|
|
||||||
|
# Only unit tests (fast, no database)
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest -m unit
|
||||||
|
|
||||||
|
# Only integration tests (with database)
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest -m integration
|
||||||
|
|
||||||
|
# Only API tests
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest -m api
|
||||||
|
|
||||||
|
# Only executor tests
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest -m executor
|
||||||
|
|
||||||
|
# Specific test file
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest tests/test_api.py -v
|
||||||
|
|
||||||
|
# With detailed output
|
||||||
|
docker exec -w /app scheduler /venv/bin/pytest -vv --tb=long
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
**Current Coverage: 80%**
|
||||||
|
|
||||||
|
```
|
||||||
|
Module Coverage
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
config.py 100% ✅
|
||||||
|
example_executor.py 100% ✅
|
||||||
|
main.py (API endpoints) 95% ✅
|
||||||
|
doc_sync_executor.py 78% ✅
|
||||||
|
executor.py (core logic) 71% ✅
|
||||||
|
config_backup_executor.py 50% 📈
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
TOTAL 80% 🎯
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Database
|
||||||
|
|
||||||
|
Tests use a dedicated PostgreSQL database:
|
||||||
|
- **Database**: `test_scheduler`
|
||||||
|
- **User**: `test_scheduler_user`
|
||||||
|
- **Location**: `postgres-shared` container
|
||||||
|
|
||||||
|
Tests automatically:
|
||||||
|
- Clean database before each test
|
||||||
|
- Insert test fixtures
|
||||||
|
- Verify database state
|
||||||
|
- Rollback changes after test
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
scheduler/
|
||||||
|
├── src/
|
||||||
|
│ ├── config.py # Pydantic settings
|
||||||
|
│ ├── main.py # FastAPI app & endpoints
|
||||||
|
│ ├── executors/ # Task executors
|
||||||
|
│ │ ├── example_executor.py
|
||||||
|
│ │ ├── doc_sync_executor.py
|
||||||
|
│ │ └── config_backup_executor.py
|
||||||
|
│ └── tasks/
|
||||||
|
│ └── executor.py # Task execution engine
|
||||||
|
├── tests/
|
||||||
|
│ ├── conftest.py # Pytest fixtures
|
||||||
|
│ ├── test_api.py # API tests
|
||||||
|
│ ├── test_*_executor.py # Executor tests
|
||||||
|
│ └── test_database_integration.py
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
├── pytest.ini # Test configuration
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to requirements.txt with version pinning
|
||||||
|
echo "new-package~=1.0.0" >> requirements.txt
|
||||||
|
|
||||||
|
# Rebuild venv in container
|
||||||
|
docker restart scheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View logs
|
||||||
|
docker logs scheduler --tail 100 -f
|
||||||
|
|
||||||
|
# Check scheduler status
|
||||||
|
curl http://localhost:8090/health
|
||||||
|
|
||||||
|
# View database tasks
|
||||||
|
docker exec postgres-shared psql -U scheduler_user -d scheduler \
|
||||||
|
-c "SELECT task_name, enabled, priority, last_status FROM scheduled_tasks;"
|
||||||
|
|
||||||
|
# View execution history
|
||||||
|
docker exec postgres-shared psql -U scheduler_user -d scheduler \
|
||||||
|
-c "SELECT task_name, status, duration_seconds, completed_at FROM task_executions ORDER BY id DESC LIMIT 10;"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Required environment variables (set in `stacks/scheduler.yml`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Database
|
||||||
|
POSTGRES_HOST=postgres-shared
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=scheduler
|
||||||
|
POSTGRES_USER=scheduler_user
|
||||||
|
POSTGRES_PASSWORD=${SCHEDULER_DB_PASSWORD}
|
||||||
|
|
||||||
|
# API Security
|
||||||
|
SCHEDULER_API_KEY=${SCHEDULER_API_KEY}
|
||||||
|
|
||||||
|
# Gitea (for doc sync)
|
||||||
|
GITEA_URL=http://gitea:3000
|
||||||
|
GITEA_USER=librarian
|
||||||
|
GITEA_PASSWORD=${GITEA_PASSWORD}
|
||||||
|
|
||||||
|
# Redis (future use)
|
||||||
|
REDIS_HOST=redis-shared
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DB=3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Container health
|
||||||
|
docker ps | grep scheduler
|
||||||
|
|
||||||
|
# API health
|
||||||
|
curl http://localhost:8090/health
|
||||||
|
|
||||||
|
# Scheduler running
|
||||||
|
curl http://localhost:8090/stats | jq '.scheduler_running'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# System statistics
|
||||||
|
curl -H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
http://localhost:8090/stats | jq
|
||||||
|
|
||||||
|
# Recent executions
|
||||||
|
curl -H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
"http://localhost:8090/executions?limit=20" | jq
|
||||||
|
|
||||||
|
# Failed tasks in last 24h
|
||||||
|
curl -H "Authorization: Bearer $SCHEDULER_API_KEY" \
|
||||||
|
"http://localhost:8090/executions?status=failed" | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Task not running?**
|
||||||
|
1. Check if task is enabled: `GET /tasks/{name}`
|
||||||
|
2. Verify schedule matches current time
|
||||||
|
3. Check execution history for errors: `GET /executions?task_name={name}`
|
||||||
|
4. View scheduler logs: `docker logs scheduler`
|
||||||
|
|
||||||
|
**Task timing out?**
|
||||||
|
- Increase `timeout_seconds` in task configuration
|
||||||
|
- Check executor implementation for long-running operations
|
||||||
|
- Consider breaking into smaller tasks
|
||||||
|
|
||||||
|
**Database connection errors?**
|
||||||
|
- Verify postgres-shared container is running
|
||||||
|
- Check database credentials in environment
|
||||||
|
- Test connection: `docker exec scheduler psql -h postgres-shared -U scheduler_user -d scheduler`
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
Full OpenAPI documentation available at: `http://localhost:8090/docs`
|
||||||
|
|
||||||
|
### Response Codes
|
||||||
|
|
||||||
|
- `200` - Success
|
||||||
|
- `400` - Bad request (invalid data)
|
||||||
|
- `401` - Missing API key
|
||||||
|
- `403` - Invalid API key
|
||||||
|
- `404` - Resource not found
|
||||||
|
- `500` - Internal server error
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
|
||||||
|
Use `limit` parameter for controlling result count:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GET /executions?limit=50 # Default: 20, Max: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filtering
|
||||||
|
|
||||||
|
Most list endpoints support filtering:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GET /tasks?enabled=true&service=scheduler
|
||||||
|
GET /executions?task_name=my_task&status=success&limit=10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- **API Key Authentication**: Required for all protected endpoints
|
||||||
|
- **Network Isolation**: Runs on `docker-dataplane` network
|
||||||
|
- **Database Credentials**: Stored in environment variables
|
||||||
|
- **Gitea Tokens**: Used instead of passwords for Git operations
|
||||||
|
- **Resource Limits**: CPU and memory limits in docker-compose
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Concurrent Execution**: Max 5 tasks simultaneously
|
||||||
|
- **Minute-based Processing**: Lightweight scheduler tick every minute
|
||||||
|
- **Priority Queue**: Higher priority tasks execute first
|
||||||
|
- **Database Indexes**: Optimized queries for task selection
|
||||||
|
- **Connection Pooling**: Reuses database connections
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Part of Portainer Core infrastructure.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
1. Check logs: `docker logs scheduler`
|
||||||
|
2. View health: `curl http://localhost:8090/health`
|
||||||
|
3. Review execution history: `GET /executions`
|
||||||
|
4. Check this README for common solutions
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Task execution system for The Scheduler."""
|
||||||
Reference in New Issue
Block a user