Skip to content

Commit a641de4

Browse files
FicoHugithub-actions[bot]yunpeng7claude
authored
Fix k8s namespace (#958)
* docs: update contributors list * docs: update contributors list (zh) * fix: fix k8s namespace handling in execution dispatcher and recovery service Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修改测试用例中的用户名为user7 * fix: 修正测试用例中的executor_name以匹配实际值 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: yunpeng7 <yunpeng7@staff.weibo.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23e1d22 commit a641de4

13 files changed

Lines changed: 364 additions & 170 deletions

File tree

backend/app/services/execution/__init__.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
- ExecutorRecoveryService: Recovers executor Pods after deletion
1515
"""
1616

17+
from shared.models import ExecutionRequest
18+
1719
from .dispatcher import ExecutionDispatcher, execution_dispatcher
1820
from .emitters import (
1921
BaseResultEmitter,
@@ -35,24 +37,13 @@
3537
from .schedule_helper import schedule_dispatch
3638

3739

38-
def get_sandbox_manager():
39-
"""Get the SandboxManager instance for creating sandboxes.
40-
41-
Returns:
42-
SandboxManager instance from executor_manager service
43-
"""
44-
import httpx
45-
46-
from app.core.config import settings
47-
48-
# For recovery, we need to call executor_manager's sandbox API
49-
# This is a thin wrapper that will be replaced with direct call
50-
# when executor_manager is properly integrated
51-
return _SandboxManagerClient()
40+
def get_executor_runtime_client():
41+
"""Get the executor runtime client for runtime preparation APIs."""
42+
return _ExecutorRuntimeClient()
5243

5344

54-
class _SandboxManagerClient:
55-
"""Thin client for calling executor_manager sandbox API."""
45+
class _ExecutorRuntimeClient:
46+
"""Thin client for calling executor_manager runtime APIs."""
5647

5748
async def create_sandbox(
5849
self,
@@ -109,6 +100,35 @@ def __init__(self, data):
109100
except Exception as e:
110101
return None, str(e)
111102

103+
async def prepare_executor(self, request: ExecutionRequest):
104+
"""Prepare a normal executor runtime without dispatching the task."""
105+
import httpx
106+
107+
from app.core.config import settings
108+
109+
base_url = settings.EXECUTOR_MANAGER_URL.rstrip("/")
110+
url = f"{base_url}/executor-manager/executors/prepare"
111+
112+
try:
113+
async with httpx.AsyncClient(timeout=180.0) as client:
114+
response = await client.post(
115+
url,
116+
json=request.to_dict(),
117+
headers={"Content-Type": "application/json"},
118+
)
119+
response.raise_for_status()
120+
data = response.json()
121+
122+
class SimpleSandbox:
123+
def __init__(self, data):
124+
self.container_name = data.get("executor_name")
125+
self.executor_namespace = data.get("executor_namespace")
126+
self.metadata = data
127+
128+
return SimpleSandbox(data), None
129+
except Exception as e:
130+
return None, str(e)
131+
112132

113133
__all__ = [
114134
# Router
@@ -125,7 +145,7 @@ def __init__(self, data):
125145
# Recovery Service
126146
"ExecutorRecoveryService",
127147
"recovery_service",
128-
"get_sandbox_manager",
148+
"get_executor_runtime_client",
129149
# Emitters - Protocol
130150
"ResultEmitter",
131151
"StreamableEmitter",

backend/app/services/execution/dispatcher.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -446,13 +446,6 @@ async def _recover_executor_if_needed(self, request: ExecutionRequest) -> None:
446446
f"Task {request.task_id} not found for executor recovery"
447447
)
448448

449-
user_id = request.user.get("id") if request.user else request.user_id
450-
user_name = (
451-
request.user.get("name")
452-
if request.user and request.user.get("name")
453-
else request.user_name
454-
)
455-
456449
logger.info(
457450
"[ExecutionDispatcher] Recovering deleted executor: "
458451
"task_id=%s, subtask_id=%s, executor=%s",
@@ -465,8 +458,7 @@ async def _recover_executor_if_needed(self, request: ExecutionRequest) -> None:
465458
db=db,
466459
subtask=subtask,
467460
task=task,
468-
user_id=user_id,
469-
user_name=user_name,
461+
request=request,
470462
)
471463
if not recovered:
472464
raise RuntimeError(

backend/app/services/execution/recovery_service.py

Lines changed: 44 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616
with git clone enabled.
1717
"""
1818

19-
import asyncio
2019
import logging
2120
from typing import Optional
2221

2322
from sqlalchemy.orm import Session
2423

2524
from app.models.subtask import Subtask
2625
from app.models.task import TaskResource
27-
from app.schemas.kind import Task
2826
from app.services.workspace_archive import archive_service
27+
from shared.models import ExecutionRequest
2928

3029
logger = logging.getLogger(__name__)
3130

@@ -74,17 +73,15 @@ async def recover(
7473
db: Session,
7574
subtask: Subtask,
7675
task: TaskResource,
77-
user_id: int,
78-
user_name: str,
76+
request: ExecutionRequest,
7977
) -> bool:
8078
"""Recover executor Pod for a task.
8179
8280
Args:
8381
db: Database session
8482
subtask: Subtask with executor_deleted_at=True
8583
task: Parent task
86-
user_id: User ID
87-
user_name: Username
84+
request: Execution request carrying the normal executor config
8885
8986
Returns:
9087
True if recovery successful, False otherwise
@@ -122,8 +119,7 @@ async def recover(
122119
db=db,
123120
subtask=subtask,
124121
task=task,
125-
user_id=user_id,
126-
user_name=user_name,
122+
request=request,
127123
)
128124
else:
129125
logger.info(
@@ -134,8 +130,7 @@ async def recover(
134130
db=db,
135131
subtask=subtask,
136132
task=task,
137-
user_id=user_id,
138-
user_name=user_name,
133+
request=request,
139134
)
140135

141136
except RuntimeError:
@@ -152,8 +147,7 @@ async def _recover_with_archive(
152147
db: Session,
153148
subtask: Subtask,
154149
task: TaskResource,
155-
user_id: int,
156-
user_name: str,
150+
request: ExecutionRequest,
157151
) -> bool:
158152
"""Recover Pod using workspace archive.
159153
@@ -165,33 +159,29 @@ async def _recover_with_archive(
165159
db: Database session
166160
subtask: Subtask
167161
task: Task
168-
user_id: User ID
169-
user_name: Username
162+
request: Execution request carrying the normal executor config
170163
171164
Returns:
172165
True if successful, False otherwise
173166
"""
174167
task_id = task.id
175168

176169
try:
177-
# Create Pod with skip_git_clone=true
178-
sandbox, error = await self._create_sandbox(
179-
task=task,
180-
subtask=subtask,
181-
user_id=user_id,
182-
user_name=user_name,
183-
skip_git_clone=True,
184-
)
170+
request.skip_git_clone = True
171+
request.executor_name = None
172+
request.executor_namespace = None
185173

186-
if error or not sandbox:
174+
executor, error = await self._prepare_executor(request, True)
175+
176+
if error or not executor:
187177
logger.error(
188-
f"[RecoveryService] Failed to create sandbox for task {task_id}: {error}"
178+
f"[RecoveryService] Failed to prepare executor for task {task_id}: {error}"
189179
)
190180
return False
191181

192-
executor_name = sandbox.container_name
182+
executor_name = executor.container_name
193183
executor_namespace = self._resolve_executor_namespace(
194-
sandbox=sandbox,
184+
sandbox=executor,
195185
previous_namespace=subtask.executor_namespace,
196186
)
197187

@@ -213,6 +203,8 @@ async def _recover_with_archive(
213203
subtask.executor_name = executor_name
214204
subtask.executor_namespace = executor_namespace
215205
subtask.executor_deleted_at = False
206+
request.executor_name = executor_name
207+
request.executor_namespace = executor_namespace
216208
db.add(subtask)
217209
db.commit()
218210

@@ -235,8 +227,7 @@ async def _recover_without_archive(
235227
db: Session,
236228
subtask: Subtask,
237229
task: TaskResource,
238-
user_id: int,
239-
user_name: str,
230+
request: ExecutionRequest,
240231
) -> bool:
241232
"""Recover Pod with normal git clone (no archive).
242233
@@ -247,40 +238,38 @@ async def _recover_without_archive(
247238
db: Database session
248239
subtask: Subtask
249240
task: Task
250-
user_id: User ID
251-
user_name: Username
241+
request: Execution request carrying the normal executor config
252242
253243
Returns:
254244
True if successful, False otherwise
255245
"""
256246
task_id = task.id
257247

258248
try:
259-
# Create Pod normally
260-
sandbox, error = await self._create_sandbox(
261-
task=task,
262-
subtask=subtask,
263-
user_id=user_id,
264-
user_name=user_name,
265-
skip_git_clone=False,
266-
)
249+
request.skip_git_clone = False
250+
request.executor_name = None
251+
request.executor_namespace = None
252+
253+
executor, error = await self._prepare_executor(request, False)
267254

268-
if error or not sandbox:
255+
if error or not executor:
269256
logger.error(
270-
f"[RecoveryService] Failed to create sandbox for task {task_id}: {error}"
257+
f"[RecoveryService] Failed to prepare executor for task {task_id}: {error}"
271258
)
272259
return False
273260

274-
executor_name = sandbox.container_name
261+
executor_name = executor.container_name
275262
executor_namespace = self._resolve_executor_namespace(
276-
sandbox=sandbox,
263+
sandbox=executor,
277264
previous_namespace=subtask.executor_namespace,
278265
)
279266

280267
# Update subtask with new executor info and reset deleted flag
281268
subtask.executor_name = executor_name
282269
subtask.executor_namespace = executor_namespace
283270
subtask.executor_deleted_at = False
271+
request.executor_name = executor_name
272+
request.executor_namespace = executor_namespace
284273
db.add(subtask)
285274
db.commit()
286275

@@ -298,62 +287,32 @@ async def _recover_without_archive(
298287
)
299288
return False
300289

301-
async def _create_sandbox(
290+
async def _prepare_executor(
302291
self,
303-
task: TaskResource,
304-
subtask: Subtask,
305-
user_id: int,
306-
user_name: str,
292+
request: ExecutionRequest,
307293
skip_git_clone: bool,
308294
):
309-
"""Create a new sandbox Pod for the task.
295+
"""Prepare a normal executor runtime without initial dispatch.
310296
311297
Args:
312-
task: Task resource
313-
subtask: Subtask
314-
user_id: User ID
315-
user_name: Username
298+
request: Execution request carrying the normal executor config
316299
skip_git_clone: Whether to skip git clone
317300
318301
Returns:
319-
Tuple of (Sandbox, error_message)
302+
Tuple of (executor runtime, error_message)
320303
"""
321-
from app.services.execution import get_sandbox_manager
322-
323-
task_crd = Task.model_validate(task.json)
324-
325-
# Get shell type from subtask or task
326-
shell_type = "ClaudeCode" # Default
304+
from app.services.execution import get_executor_runtime_client
327305

328-
# Build bot_config from task
329-
bot_config = {}
330-
331-
# Build metadata
332-
metadata = {
333-
"task_id": task.id,
334-
"subtask_id": subtask.id,
335-
"skip_git_clone": skip_git_clone,
336-
}
337-
if subtask.executor_namespace:
338-
metadata["executor_namespace"] = subtask.executor_namespace
339-
340-
# Get workspace ref if available
341-
workspace_ref = (
342-
task_crd.spec.workspaceRef.name if task_crd.spec.workspaceRef else None
343-
)
306+
runtime_client = get_executor_runtime_client()
307+
request.skip_git_clone = skip_git_clone
308+
request.executor_name = None
309+
request.executor_namespace = None
344310

345-
# Create sandbox
346-
sandbox_manager = get_sandbox_manager()
347-
sandbox, error = await sandbox_manager.create_sandbox(
348-
shell_type=shell_type,
349-
user_id=user_id,
350-
user_name=user_name,
351-
workspace_ref=workspace_ref,
352-
bot_config=bot_config,
353-
metadata=metadata,
311+
executor, error = await runtime_client.prepare_executor(
312+
request=request,
354313
)
355314

356-
return sandbox, error
315+
return executor, error
357316

358317

359318
# Global service instance

0 commit comments

Comments
 (0)