198 lines
7.4 KiB
Python
198 lines
7.4 KiB
Python
"""Background schedulers, cleanup loops, and development fixture setup."""
|
|
|
|
|
|
from currency_service import fetch_exchange_rates
|
|
from database_service import database_connection, dict_from_row
|
|
from domain_service import cleanup_orphaned_ingressroutes
|
|
from kubernetes_service import k8s_manager
|
|
import os
|
|
from runtime_config import (
|
|
CACHE_DURATION,
|
|
CHUNK_UPLOAD_DIR,
|
|
chunked_uploads,
|
|
logger,
|
|
)
|
|
import schedule
|
|
from server_demo import cleanup_expired_demo_servers
|
|
from server_provisioning import sync_server_ports_from_k8s, sync_server_status_from_k8s
|
|
import threading
|
|
import time
|
|
from user_service import create_user, get_user_by_username
|
|
|
|
__all__ = ['cleanup_orphaned_uploads', 'initialize_sample_data', 'start_demo_cleanup_scheduler', 'start_exchange_rate_updater', 'start_ingress_cleanup_thread', 'start_status_sync_thread', 'start_upload_cleanup_scheduler', 'sync_all_servers_status']
|
|
|
|
|
|
|
|
def cleanup_orphaned_uploads():
|
|
"""Clean up orphaned chunk upload files and sessions older than 1 hour"""
|
|
try:
|
|
current_time = time.time()
|
|
max_age = 3600 # 1 hour
|
|
|
|
# Clean up old upload sessions
|
|
uploads_to_remove = []
|
|
for upload_id, upload_info in chunked_uploads.items():
|
|
start_time = upload_info.get('start_time', current_time)
|
|
if current_time - start_time > max_age:
|
|
uploads_to_remove.append(upload_id)
|
|
|
|
for upload_id in uploads_to_remove:
|
|
upload_info = chunked_uploads[upload_id]
|
|
temp_file = upload_info.get('temp_file')
|
|
if temp_file and os.path.exists(temp_file):
|
|
try:
|
|
os.remove(temp_file)
|
|
logger.info(f"Cleaned up orphaned upload temp file: {temp_file}")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to remove orphaned temp file {temp_file}: {e}")
|
|
del chunked_uploads[upload_id]
|
|
logger.info(f"Cleaned up orphaned upload session: {upload_id}")
|
|
|
|
# Clean up any remaining temp files older than 1 hour
|
|
try:
|
|
for filename in os.listdir(CHUNK_UPLOAD_DIR):
|
|
filepath = os.path.join(CHUNK_UPLOAD_DIR, filename)
|
|
if os.path.isfile(filepath):
|
|
file_age = current_time - os.path.getmtime(filepath)
|
|
if file_age > max_age:
|
|
try:
|
|
os.remove(filepath)
|
|
logger.info(f"Cleaned up old temp file: {filepath}")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to remove old temp file {filepath}: {e}")
|
|
except Exception as e:
|
|
logger.warning(f"Error cleaning up temp files directory: {e}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in cleanup_orphaned_uploads: {e}")
|
|
|
|
def start_upload_cleanup_scheduler():
|
|
"""Start scheduler to clean up orphaned uploads"""
|
|
def run_scheduler():
|
|
while True:
|
|
schedule.run_pending()
|
|
time.sleep(60) # Check every minute
|
|
|
|
# Run cleanup every 30 minutes
|
|
schedule.every(30).minutes.do(cleanup_orphaned_uploads)
|
|
thread = threading.Thread(target=run_scheduler, daemon=True)
|
|
thread.start()
|
|
logger.info("🧹 Upload cleanup scheduler started")
|
|
|
|
def start_demo_cleanup_scheduler():
|
|
"""Start scheduler to clean up expired demo servers"""
|
|
def run_scheduler():
|
|
while True:
|
|
schedule.run_pending()
|
|
time.sleep(60) # Check every minute
|
|
|
|
# Run cleanup every minute to ensure immediate deletion of expired demo servers
|
|
schedule.every(1).minutes.do(cleanup_expired_demo_servers)
|
|
thread = threading.Thread(target=run_scheduler, daemon=True)
|
|
thread.start()
|
|
logger.info("🧹 Demo cleanup scheduler started (runs every minute)")
|
|
|
|
def start_exchange_rate_updater():
|
|
"""Background thread to update exchange rates periodically"""
|
|
def update_loop():
|
|
# Initial fetch
|
|
fetch_exchange_rates()
|
|
|
|
while True:
|
|
try:
|
|
time.sleep(CACHE_DURATION) # Wait 1 hour
|
|
fetch_exchange_rates()
|
|
except Exception as e:
|
|
logger.error(f"Error in exchange rate update loop: {e}")
|
|
time.sleep(60) # Wait 1 minute before retrying
|
|
|
|
thread = threading.Thread(target=update_loop, daemon=True)
|
|
thread.start()
|
|
logger.info("✓ Started exchange rate updater thread")
|
|
|
|
def sync_all_servers_status():
|
|
"""Sync status for all servers"""
|
|
if not k8s_manager.connected:
|
|
return
|
|
|
|
try:
|
|
with database_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT id, owner_id FROM servers WHERE status IN ('Creating', 'Starting', 'Stopping', 'Restarting')")
|
|
servers = [dict_from_row(row) for row in cursor.fetchall()]
|
|
|
|
for server in servers:
|
|
try:
|
|
sync_server_status_from_k8s(server['id'], server['owner_id'])
|
|
sync_server_ports_from_k8s(server['id'], server['owner_id'])
|
|
except Exception as e:
|
|
logger.error(f"Error syncing server {server['id']}: {e}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error syncing all servers: {e}")
|
|
|
|
def start_status_sync_thread():
|
|
"""Start background status sync"""
|
|
def sync_loop():
|
|
while True:
|
|
try:
|
|
sync_all_servers_status()
|
|
time.sleep(30)
|
|
except Exception as e:
|
|
logger.error(f"Error in sync loop: {e}")
|
|
time.sleep(60)
|
|
|
|
thread = threading.Thread(target=sync_loop, daemon=True)
|
|
thread.start()
|
|
logger.info("Started background status sync")
|
|
|
|
def initialize_sample_data():
|
|
"""Create development fixtures only when explicitly enabled."""
|
|
if os.environ.get('ENABLE_SAMPLE_DATA', '').casefold() != 'true':
|
|
logger.info("Skipping sample data; set ENABLE_SAMPLE_DATA=true to opt in")
|
|
return
|
|
|
|
sample_password = os.environ.get('SAMPLE_USER_PASSWORD')
|
|
if not sample_password:
|
|
logger.warning("Skipping sample user creation; SAMPLE_USER_PASSWORD is not configured")
|
|
return
|
|
|
|
logger.info("Initializing sample data...")
|
|
|
|
k8s_manager.test_connection()
|
|
|
|
existing_user = get_user_by_username('testuser')
|
|
if not existing_user:
|
|
sample_user = create_user(
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password=sample_password,
|
|
credits=100
|
|
)
|
|
if sample_user:
|
|
logger.info(f"✓ Sample user created: {sample_user['username']}")
|
|
else:
|
|
logger.info(f"✓ Sample user exists: {existing_user['username']}")
|
|
|
|
if k8s_manager.connected:
|
|
try:
|
|
k8s_manager.create_namespace('user-testuser')
|
|
logger.info("✓ Sample namespace created")
|
|
except Exception as e:
|
|
logger.error(f"⚠ Could not create namespace: {e}")
|
|
|
|
def start_ingress_cleanup_thread():
|
|
"""Start background thread to clean up orphaned IngressRoutes"""
|
|
def cleanup_loop():
|
|
while True:
|
|
try:
|
|
cleanup_orphaned_ingressroutes()
|
|
time.sleep(300) # Run every 5 minutes
|
|
except Exception as e:
|
|
logger.error(f"Error in ingress cleanup loop: {e}")
|
|
time.sleep(60)
|
|
|
|
thread = threading.Thread(target=cleanup_loop, daemon=True)
|
|
thread.start()
|
|
logger.info("Started background IngressRoute cleanup")
|