New enterprise feature
This commit is contained in:
@@ -16,6 +16,10 @@ from app.models.demo_session import CloningStatus
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
# Import json for Redis serialization
|
||||
import json
|
||||
|
||||
|
||||
class ServiceDefinition:
|
||||
"""Definition of a service that can clone demo data"""
|
||||
|
||||
@@ -29,9 +33,10 @@ class ServiceDefinition:
|
||||
class CloneOrchestrator:
|
||||
"""Orchestrates parallel demo data cloning across services"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, redis_manager=None):
|
||||
from app.core.config import settings
|
||||
self.internal_api_key = settings.INTERNAL_API_KEY
|
||||
self.redis_manager = redis_manager # For real-time progress updates
|
||||
|
||||
# Define services that participate in cloning
|
||||
# URLs should be internal Kubernetes service names
|
||||
@@ -110,6 +115,66 @@ class CloneOrchestrator:
|
||||
),
|
||||
]
|
||||
|
||||
async def _update_progress_in_redis(
|
||||
self,
|
||||
session_id: str,
|
||||
progress_data: Dict[str, Any]
|
||||
):
|
||||
"""Update cloning progress in Redis for real-time frontend polling"""
|
||||
if not self.redis_manager:
|
||||
return # Skip if no Redis manager provided
|
||||
|
||||
try:
|
||||
status_key = f"session:{session_id}:status"
|
||||
client = await self.redis_manager.get_client()
|
||||
|
||||
# Get existing status data or create new
|
||||
existing_data_str = await client.get(status_key)
|
||||
if existing_data_str:
|
||||
status_data = json.loads(existing_data_str)
|
||||
else:
|
||||
# Initialize basic status structure
|
||||
status_data = {
|
||||
"session_id": session_id,
|
||||
"status": "pending",
|
||||
"progress": {},
|
||||
"total_records_cloned": 0
|
||||
}
|
||||
|
||||
# Update progress field with new data
|
||||
status_data["progress"] = progress_data
|
||||
|
||||
# Calculate total records cloned from progress
|
||||
total_records = 0
|
||||
if "parent" in progress_data and "total_records_cloned" in progress_data["parent"]:
|
||||
total_records += progress_data["parent"]["total_records_cloned"]
|
||||
if "children" in progress_data:
|
||||
for child in progress_data["children"]:
|
||||
if isinstance(child, dict) and "records_cloned" in child:
|
||||
total_records += child["records_cloned"]
|
||||
|
||||
status_data["total_records_cloned"] = total_records
|
||||
|
||||
# Update Redis with 2-hour TTL
|
||||
await client.setex(
|
||||
status_key,
|
||||
7200, # 2 hours
|
||||
json.dumps(status_data)
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Updated progress in Redis",
|
||||
session_id=session_id,
|
||||
progress_keys=list(progress_data.keys())
|
||||
)
|
||||
except Exception as e:
|
||||
# Don't fail cloning if progress update fails
|
||||
logger.warning(
|
||||
"Failed to update progress in Redis",
|
||||
session_id=session_id,
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
async def clone_all_services(
|
||||
self,
|
||||
base_tenant_id: str,
|
||||
@@ -535,6 +600,14 @@ class CloneOrchestrator:
|
||||
try:
|
||||
# Step 1: Clone parent tenant
|
||||
logger.info("Cloning parent tenant", session_id=session_id)
|
||||
|
||||
# Update progress: Parent cloning started
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": {"overall_status": "pending"},
|
||||
"children": [],
|
||||
"distribution": {}
|
||||
})
|
||||
|
||||
parent_result = await self.clone_all_services(
|
||||
base_tenant_id=base_tenant_id,
|
||||
virtual_tenant_id=parent_tenant_id,
|
||||
@@ -543,6 +616,13 @@ class CloneOrchestrator:
|
||||
)
|
||||
results["parent"] = parent_result
|
||||
|
||||
# Update progress: Parent cloning completed
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": parent_result,
|
||||
"children": [],
|
||||
"distribution": {}
|
||||
})
|
||||
|
||||
# BUG-006 FIX: Track parent for potential rollback
|
||||
if parent_result.get("overall_status") not in ["failed"]:
|
||||
rollback_stack.append({
|
||||
@@ -599,6 +679,13 @@ class CloneOrchestrator:
|
||||
child_count=len(child_configs)
|
||||
)
|
||||
|
||||
# Update progress: Children cloning started
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": parent_result,
|
||||
"children": [{"status": "pending"} for _ in child_configs],
|
||||
"distribution": {}
|
||||
})
|
||||
|
||||
child_tasks = []
|
||||
for idx, (child_config, child_id) in enumerate(zip(child_configs, child_tenant_ids)):
|
||||
task = self._clone_child_outlet(
|
||||
@@ -617,6 +704,13 @@ class CloneOrchestrator:
|
||||
for r in children_results
|
||||
]
|
||||
|
||||
# Update progress: Children cloning completed
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": parent_result,
|
||||
"children": results["children"],
|
||||
"distribution": {}
|
||||
})
|
||||
|
||||
# BUG-006 FIX: Track children for potential rollback
|
||||
for child_result in results["children"]:
|
||||
if child_result.get("status") not in ["failed"]:
|
||||
@@ -630,6 +724,13 @@ class CloneOrchestrator:
|
||||
distribution_url = os.getenv("DISTRIBUTION_SERVICE_URL", "http://distribution-service:8000")
|
||||
logger.info("Setting up distribution data", session_id=session_id, distribution_url=distribution_url)
|
||||
|
||||
# Update progress: Distribution starting
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": parent_result,
|
||||
"children": results["children"],
|
||||
"distribution": {"status": "pending"}
|
||||
})
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=120.0) as client: # Increased timeout for distribution setup
|
||||
response = await client.post(
|
||||
@@ -646,6 +747,13 @@ class CloneOrchestrator:
|
||||
if response.status_code == 200:
|
||||
results["distribution"] = response.json()
|
||||
logger.info("Distribution setup completed successfully", session_id=session_id)
|
||||
|
||||
# Update progress: Distribution completed
|
||||
await self._update_progress_in_redis(session_id, {
|
||||
"parent": parent_result,
|
||||
"children": results["children"],
|
||||
"distribution": results["distribution"]
|
||||
})
|
||||
else:
|
||||
error_detail = response.text if response.text else f"HTTP {response.status_code}"
|
||||
results["distribution"] = {
|
||||
|
||||
@@ -27,7 +27,7 @@ class DemoSessionManager:
|
||||
self.db = db
|
||||
self.redis = redis
|
||||
self.repository = DemoSessionRepository(db)
|
||||
self.orchestrator = CloneOrchestrator()
|
||||
self.orchestrator = CloneOrchestrator(redis_manager=redis) # Pass Redis for real-time progress updates
|
||||
|
||||
async def create_session(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user