feat: on-demand RDP - connect xfreerdp only when session opens
Replaces always-on xfreerdp with on-demand model (load 12 to under 1 at idle). - rdp-proxy/manager.py: HTTP server port 7001 managing xfreerdp lifecycle - rdp-proxy/entrypoint.sh: starts Xvfb+x11vnc+websockify+manager, no auto-connect - rdp-proxy/Dockerfile: adds python3, copies manager.py, exposes 7001 - runtime.py: connect_rdp_slot and disconnect_rdp_slot via manager HTTP API - terminate_session_record: disconnect instead of container restart - main.py: calls connect_rdp_slot in background thread on session create - maintenance.py: cleanup_loop disconnects on expire, run_maintenance_service includes RDP slot init, maintenance_runner fixed to import maintenance
This commit is contained in:
+3
-1
@@ -1,6 +1,7 @@
|
||||
import datetime as dt
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
import uuid
|
||||
import time
|
||||
import contextvars
|
||||
@@ -46,7 +47,7 @@ from runtime import (
|
||||
get_universal_pool_status, get_web_pool_status, LockTimeoutError, open_warm_web_url,
|
||||
_rdp_slot_container_name, route_ready, sanitize_client_resolution,
|
||||
service_uses_universal_pool, session_redirect_url,
|
||||
start_rdp_slot_container, stop_rdp_slot_container,
|
||||
connect_rdp_slot, start_rdp_slot_container, stop_rdp_slot_container,
|
||||
stop_runtime_container, terminate_active_slot_sessions,
|
||||
terminate_session_record, wait_for_session_route,
|
||||
)
|
||||
@@ -579,6 +580,7 @@ def go_service(
|
||||
log_event("session_created", user_id=user.id, service_slug=service.slug, session_id=session_id, mode="rdp_slot", slot_id=free_slot.id)
|
||||
audit(db, "SESSION_CREATE_RDP_SLOT", f"service={service.slug} session={session_id} slot={free_slot.id}", user_id=user.id)
|
||||
_emit("session_created_rdp_slot", session_id=session_id, slot_id=free_slot.id)
|
||||
threading.Thread(target=connect_rdp_slot, args=(free_slot.id,), daemon=True).start()
|
||||
return RedirectResponse(url=f"/s/{session_id}/", status_code=303)
|
||||
else:
|
||||
# Legacy: no slots configured — exclusive single-session behaviour
|
||||
|
||||
+18
-2
@@ -15,7 +15,7 @@ from utils import ensure_icons_dir, now_utc
|
||||
from auth import hash_password
|
||||
from runtime import (
|
||||
_rdp_slot_container_name,
|
||||
_restart_rdp_slot_bg,
|
||||
disconnect_rdp_slot,
|
||||
docker_client,
|
||||
ensure_schema_compatibility,
|
||||
ensure_universal_pool,
|
||||
@@ -68,7 +68,7 @@ def cleanup_loop():
|
||||
if stale:
|
||||
db.commit()
|
||||
for slot_id in rdp_slots_to_restart:
|
||||
threading.Thread(target=_restart_rdp_slot_bg, args=(slot_id,), daemon=True).start()
|
||||
threading.Thread(target=disconnect_rdp_slot, args=(slot_id,), daemon=True).start()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
logger.exception("cleanup_loop_failed")
|
||||
@@ -140,6 +140,22 @@ def run_maintenance_service() -> None:
|
||||
).all():
|
||||
if svc.type == ServiceType.WEB and WEB_POOL_SIZE <= 0:
|
||||
ensure_warm_pool(svc)
|
||||
elif svc.type == ServiceType.RDP:
|
||||
slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id)).all()
|
||||
for slot in slots:
|
||||
try:
|
||||
cname = _rdp_slot_container_name(svc.slug, slot.id)
|
||||
try:
|
||||
c = docker_client().containers.get(cname)
|
||||
if c.status != "running":
|
||||
c.start()
|
||||
except docker.errors.NotFound:
|
||||
start_rdp_slot_container(slot, svc)
|
||||
slot.container_name = cname
|
||||
except Exception:
|
||||
logger.exception("startup_rdp_slot_start_failed slot_id=%s", slot.id)
|
||||
if slots:
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import main
|
||||
|
||||
import maintenance
|
||||
|
||||
if __name__ == "__main__":
|
||||
main.run_maintenance_service()
|
||||
maintenance.run_maintenance_service()
|
||||
|
||||
+33
-2
@@ -670,6 +670,37 @@ def stop_rdp_slot_container(container_name: str) -> None:
|
||||
logger.exception("rdp_slot_container_stop_failed container=%s", container_name)
|
||||
|
||||
|
||||
def _call_rdp_manager(container_name: str, endpoint: str) -> bool:
|
||||
url = f"http://{container_name}:7001{endpoint}"
|
||||
try:
|
||||
resp = requests.post(url, timeout=10)
|
||||
logger.info("rdp_manager_%s container=%s status=%s", endpoint.strip('/'), container_name, resp.status_code)
|
||||
return resp.ok
|
||||
except Exception:
|
||||
logger.exception("rdp_manager_call_failed container=%s endpoint=%s", container_name, endpoint)
|
||||
return False
|
||||
|
||||
|
||||
def connect_rdp_slot(slot_id: int) -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
slot = db.get(RdpSlot, slot_id)
|
||||
if slot and slot.container_name:
|
||||
_call_rdp_manager(slot.container_name, "/connect")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def disconnect_rdp_slot(slot_id: int) -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
slot = db.get(RdpSlot, slot_id)
|
||||
if slot and slot.container_name:
|
||||
_call_rdp_manager(slot.container_name, "/disconnect")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _restart_rdp_slot_bg(slot_id: int) -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
@@ -719,9 +750,9 @@ def terminate_session_record(
|
||||
if cid.startswith("RDPSLOT:"):
|
||||
try:
|
||||
slot_id = int(cid.split(":", 1)[1])
|
||||
threading.Thread(target=_restart_rdp_slot_bg, args=(slot_id,), daemon=True).start()
|
||||
threading.Thread(target=disconnect_rdp_slot, args=(slot_id,), daemon=True).start()
|
||||
except Exception:
|
||||
logger.exception("rdp_slot_restart_schedule_failed cid=%s", cid)
|
||||
logger.exception("rdp_slot_disconnect_failed cid=%s", cid)
|
||||
sess.status = new_status
|
||||
sess.last_access_at = now_utc()
|
||||
log_event(
|
||||
|
||||
Reference in New Issue
Block a user