Add cluster API for worker management and status reporting

This commit is contained in:
Eduardo Silva
2025-12-30 11:30:55 -03:00
parent 4a6487835b
commit 92e3049a8e
10 changed files with 274 additions and 5 deletions

152
cluster/cluster_api.py Normal file
View File

@@ -0,0 +1,152 @@
import glob
import os
from django.http import JsonResponse
from django.utils import timezone
from .models import ClusterSettings, Worker, WorkerStatus
def get_ip_address(request):
ip_address = request.META.get('HTTP_X_FORWARDED_FOR')
if ip_address:
ip_address = ip_address.split(',')[0]
else:
ip_address = request.META.get('REMOTE_ADDR')
return ip_address
def get_worker(request):
min_worker_version = 1
success = True
ip_address = get_ip_address(request)
token = request.GET.get('token', '')
try:
worker = Worker.objects.get(token=token)
except:
return None, False
worker_status, created = WorkerStatus.objects.get_or_create(worker=worker)
try:
worker_config_version = int(request.GET.get('worker_config_version'))
worker_version = int(request.GET.get('worker_version'))
except:
worker.error_status = 'missing_version'
worker.save()
return worker, False
if worker.error_status == 'missing_version':
worker.error_status = ''
worker.save()
if worker_version < min_worker_version:
worker.error_status = 'update_required'
worker.save()
return worker, False
if worker.error_status == 'update_required':
worker.error_status = ''
worker.save()
if worker_status.config_version != worker_config_version:
worker_status.config_version = worker_config_version
if worker_status.worker_version != worker_version:
worker_status.worker_version = worker_version
worker_status.last_seen = timezone.now()
worker_status.save()
if not worker.ip_address:
worker.ip_address = ip_address
worker.save()
if worker.ip_lock:
if worker.ip_address == ip_address:
if worker.error_status == 'ip_lock':
worker.error_status = ''
worker.save()
else:
worker.error_status = 'ip_lock'
worker.save()
success = False
else:
if worker.ip_address != ip_address:
worker.ip_address = ip_address
worker.save()
if worker.enabled:
if worker.error_status == 'worker_disabled':
worker.error_status = ''
worker.save()
else:
worker.error_status = 'worker_disabled'
worker.save()
success = False
cluster_settings, created = ClusterSettings.objects.get_or_create(name='cluster_settings')
if cluster_settings.enabled:
if worker.error_status == 'cluster_disabled':
worker.error_status = ''
worker.save()
else:
worker.error_status = 'cluster_disabled'
worker.save()
success = False
return worker, success
def api_get_worker_config_files(request):
worker, success = get_worker(request)
if worker:
if worker.error_status or not success:
data = {'status': 'error', 'message': worker.error_status}
return JsonResponse(data, status=400)
else:
data = {'status': 'error', 'message': 'Worker not found'}
return JsonResponse(data, status=403)
config_files = (
glob.glob('/etc/wireguard/wg*.conf') +
glob.glob('/etc/wireguard/wg-firewall.sh')
)
files = {}
for path in config_files:
filename = os.path.basename(path)
with open(path, 'r') as f:
files[filename] = f.read()
return JsonResponse(
{
'status': 'success',
'files': files,
},
status=200
)
def api_cluster_status(request):
worker, success = get_worker(request)
if worker:
if worker.error_status or not success:
data = {'status': 'error', 'message': worker.error_status}
return JsonResponse(data, status=400)
else:
data = {'status': 'error', 'message': 'Worker not found'}
return JsonResponse(data, status=403)
cluster_settings, created = ClusterSettings.objects.get_or_create(name='cluster_settings')
data = {
'status': 'success',
'worker_error_status': worker.error_status,
'cluster_settings': {
'enabled': cluster_settings.enabled,
'primary_enable_wireguard': cluster_settings.primary_enable_wireguard,
'stats_sync_interval': cluster_settings.stats_sync_interval,
'stats_cache_interval': cluster_settings.stats_cache_interval,
'cluster_mode': cluster_settings.cluster_mode,
'restart_mode': cluster_settings.restart_mode,
'config_version': cluster_settings.config_version,
},
}
return JsonResponse(data, status=200)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2025-12-29 21:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cluster', '0005_alter_worker_token'),
]
operations = [
migrations.AddField(
model_name='worker',
name='error_status',
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled')], default='', max_length=32),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2025-12-30 12:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cluster', '0006_worker_error_status'),
]
operations = [
migrations.AddField(
model_name='workerstatus',
name='worker_version',
field=models.BooleanField(default=0),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2025-12-30 13:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cluster', '0007_workerstatus_worker_version'),
]
operations = [
migrations.AlterField(
model_name='worker',
name='error_status',
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.')], default='', max_length=32),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2025-12-30 14:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cluster', '0008_alter_worker_error_status'),
]
operations = [
migrations.AlterField(
model_name='worker',
name='error_status',
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.'), ('update_required', 'Worker configuration update is required.')], default='', max_length=32),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.9 on 2025-12-30 14:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cluster', '0009_alter_worker_error_status'),
]
operations = [
migrations.AlterField(
model_name='worker',
name='error_status',
field=models.CharField(choices=[('', ''), ('ip_lock', 'IP lock is enabled, but the worker is attempting to access from a different IP address.'), ('worker_disabled', 'Worker is not enabled'), ('cluster_disabled', 'Cluster is not enabled'), ('missing_version', 'Please report worker_config_version and worker_version in the API request.'), ('update_required', 'Worker update is required.')], default='', max_length=32),
),
migrations.AlterField(
model_name='workerstatus',
name='worker_version',
field=models.PositiveIntegerField(default=0),
),
]

View File

@@ -1,6 +1,7 @@
import uuid
from django.db import models
from django.utils.translation import gettext_lazy as _
class ClusterSettings(models.Model):
@@ -41,6 +42,15 @@ class Worker(models.Model):
city = models.CharField(max_length=100, blank=True, null=True)
hostname = models.CharField(max_length=100, blank=True, null=True)
error_status = models.CharField(default='', max_length=32, choices=(
('', ''),
('ip_lock', _('IP lock is enabled, but the worker is attempting to access from a different IP address.')),
('worker_disabled', _('Worker is not enabled')),
('cluster_disabled', _('Cluster is not enabled')),
('missing_version', _('Please report worker_config_version and worker_version in the API request.')),
('update_required', _('Worker update is required.'))
))
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
@@ -53,6 +63,7 @@ class WorkerStatus(models.Model):
last_restart = models.DateTimeField(blank=True, null=True)
config_version = models.PositiveIntegerField(default=0)
config_pending = models.BooleanField(default=False)
worker_version = models.PositiveIntegerField(default=0)
active_peers = models.PositiveIntegerField(default=0)
wireguard_status = models.JSONField(default=dict)

View File

@@ -16,7 +16,7 @@ def cluster_main(request):
page_title = _('Cluster')
workers = Worker.objects.all().order_by('name')
context = {'page_title': page_title, 'workers': workers}
context = {'page_title': page_title, 'workers': workers, 'worker_version': 10}
return render(request, 'cluster/workers_list.html', context)