agentscope-runtime 1.0.2__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentscope_runtime/cli/commands/deploy.py +12 -0
- agentscope_runtime/common/collections/redis_mapping.py +4 -1
- agentscope_runtime/engine/app/agent_app.py +48 -5
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +56 -1
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +449 -41
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +273 -0
- agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +640 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +3 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +8 -2
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +5 -0
- agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
- agentscope_runtime/engine/runner.py +5 -3
- agentscope_runtime/engine/schemas/exception.py +24 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
- agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
- agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
- agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
- agentscope_runtime/sandbox/build.py +50 -57
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/METADATA +9 -3
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/RECORD +25 -22
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Nacos Registry Implementation for A2A Protocol
|
|
4
|
+
|
|
5
|
+
This module provides a Nacos-based registry implementation for A2A protocol
|
|
6
|
+
agent service registration. It serves as the default registry implementation
|
|
7
|
+
for A2A protocol adapters.
|
|
8
|
+
"""
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Optional, TYPE_CHECKING, List
|
|
13
|
+
import threading
|
|
14
|
+
|
|
15
|
+
from a2a.types import AgentCard
|
|
16
|
+
|
|
17
|
+
# Make the v2.nacos imports optional to avoid hard dependency at
|
|
18
|
+
# module import time.
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from v2.nacos import ClientConfig, ClientConfigBuilder
|
|
21
|
+
from v2.nacos.ai.model.ai_param import (
|
|
22
|
+
RegisterAgentEndpointParam,
|
|
23
|
+
ReleaseAgentCardParam,
|
|
24
|
+
)
|
|
25
|
+
from v2.nacos.ai.nacos_ai_service import NacosAIService
|
|
26
|
+
|
|
27
|
+
_NACOS_SDK_AVAILABLE = True
|
|
28
|
+
else:
|
|
29
|
+
try:
|
|
30
|
+
from v2.nacos import ClientConfig, ClientConfigBuilder
|
|
31
|
+
from v2.nacos.ai.model.ai_param import (
|
|
32
|
+
RegisterAgentEndpointParam,
|
|
33
|
+
ReleaseAgentCardParam,
|
|
34
|
+
)
|
|
35
|
+
from v2.nacos.ai.nacos_ai_service import NacosAIService
|
|
36
|
+
|
|
37
|
+
_NACOS_SDK_AVAILABLE = True
|
|
38
|
+
except Exception:
|
|
39
|
+
|
|
40
|
+
class ClientConfig:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
class ClientConfigBuilder:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
class RegisterAgentEndpointParam:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
class ReleaseAgentCardParam:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
class NacosAIService:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
_NACOS_SDK_AVAILABLE = False
|
|
56
|
+
|
|
57
|
+
# Import after conditional imports to avoid circular dependencies
|
|
58
|
+
from .a2a_registry import ( # pylint: disable=wrong-import-position
|
|
59
|
+
A2ARegistry,
|
|
60
|
+
A2ATransportsProperties,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RegistrationStatus(Enum):
|
|
67
|
+
"""Registration task status."""
|
|
68
|
+
|
|
69
|
+
PENDING = "pending"
|
|
70
|
+
IN_PROGRESS = "in_progress"
|
|
71
|
+
COMPLETED = "completed"
|
|
72
|
+
FAILED = "failed"
|
|
73
|
+
CANCELLED = "cancelled"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class NacosRegistry(A2ARegistry):
|
|
77
|
+
"""Nacos-based registry implementation for A2A protocol.
|
|
78
|
+
|
|
79
|
+
This registry registers A2A agent services to Nacos service
|
|
80
|
+
discovery system. It performs two-step registration:
|
|
81
|
+
1. Publishes agent card to Nacos (agent metadata)
|
|
82
|
+
2. Registers agent endpoint with host/port information (service location)
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
nacos_client_config: Optional Nacos client configuration.
|
|
86
|
+
If None, creates default config from environment variables.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
nacos_client_config: Optional[ClientConfig] = None,
|
|
92
|
+
):
|
|
93
|
+
self._nacos_client_config = nacos_client_config
|
|
94
|
+
self._nacos_ai_service: Optional[NacosAIService] = None
|
|
95
|
+
self._register_task: Optional[asyncio.Task] = None
|
|
96
|
+
self._register_thread: Optional[threading.Thread] = None
|
|
97
|
+
self._registration_status: RegistrationStatus = (
|
|
98
|
+
RegistrationStatus.PENDING
|
|
99
|
+
)
|
|
100
|
+
self._registration_lock = threading.Lock()
|
|
101
|
+
self._shutdown_event = threading.Event()
|
|
102
|
+
|
|
103
|
+
def registry_name(self) -> str:
|
|
104
|
+
"""Get the name of this registry implementation.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Registry name: "nacos"
|
|
108
|
+
"""
|
|
109
|
+
return "nacos"
|
|
110
|
+
|
|
111
|
+
def register(
|
|
112
|
+
self,
|
|
113
|
+
agent_card: AgentCard,
|
|
114
|
+
a2a_transports_properties: Optional[
|
|
115
|
+
List[A2ATransportsProperties]
|
|
116
|
+
] = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Register an A2A agent service to Nacos.
|
|
119
|
+
|
|
120
|
+
This method initiates registration asynchronously in the
|
|
121
|
+
background. Exceptions during registration are caught and
|
|
122
|
+
logged, but not raised. Use `get_registration_status()` or
|
|
123
|
+
`wait_for_registration()` to check the result.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
agent_card: The complete A2A agent card generated by runtime
|
|
127
|
+
a2a_transports_properties: List of transport configurations.
|
|
128
|
+
Each transport will be registered separately.
|
|
129
|
+
"""
|
|
130
|
+
# If Nacos SDK is not available, log and return
|
|
131
|
+
if not _NACOS_SDK_AVAILABLE:
|
|
132
|
+
logger.debug(
|
|
133
|
+
"[NacosRegistry] Nacos SDK is not available; "
|
|
134
|
+
"skipping registration",
|
|
135
|
+
)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
self._start_register_task(
|
|
139
|
+
agent_card=agent_card,
|
|
140
|
+
a2a_transports_properties=a2a_transports_properties,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _start_register_task(
|
|
144
|
+
self,
|
|
145
|
+
agent_card: AgentCard,
|
|
146
|
+
a2a_transports_properties: Optional[
|
|
147
|
+
List[A2ATransportsProperties]
|
|
148
|
+
] = None,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Start background Nacos registration task.
|
|
151
|
+
|
|
152
|
+
If there is an active asyncio event loop, schedule the
|
|
153
|
+
registration as a task on that loop. Otherwise, spawn a daemon
|
|
154
|
+
thread and run the registration using asyncio.run so
|
|
155
|
+
registration still occurs in synchronous contexts.
|
|
156
|
+
"""
|
|
157
|
+
# All status checks and updates must be within the lock to
|
|
158
|
+
# ensure atomicity
|
|
159
|
+
with self._registration_lock:
|
|
160
|
+
# Check if shutdown was already requested (inside lock
|
|
161
|
+
# for atomicity)
|
|
162
|
+
if self._shutdown_event.is_set():
|
|
163
|
+
logger.info(
|
|
164
|
+
"[NacosRegistry] Shutdown already requested, "
|
|
165
|
+
"skipping registration",
|
|
166
|
+
)
|
|
167
|
+
self._registration_status = RegistrationStatus.CANCELLED
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Check if registration is already in progress or completed
|
|
171
|
+
if self._registration_status in (
|
|
172
|
+
RegistrationStatus.IN_PROGRESS,
|
|
173
|
+
RegistrationStatus.COMPLETED,
|
|
174
|
+
):
|
|
175
|
+
logger.warning(
|
|
176
|
+
"[NacosRegistry] Registration already in "
|
|
177
|
+
"progress or completed, skipping",
|
|
178
|
+
)
|
|
179
|
+
return
|
|
180
|
+
self._registration_status = RegistrationStatus.PENDING
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
loop = None
|
|
184
|
+
try:
|
|
185
|
+
loop = asyncio.get_running_loop()
|
|
186
|
+
except RuntimeError:
|
|
187
|
+
# No running loop in this thread; we'll fall back to a
|
|
188
|
+
# background thread
|
|
189
|
+
loop = None
|
|
190
|
+
|
|
191
|
+
if loop is not None:
|
|
192
|
+
with self._registration_lock:
|
|
193
|
+
self._registration_status = RegistrationStatus.IN_PROGRESS
|
|
194
|
+
self._register_task = loop.create_task(
|
|
195
|
+
self._register_to_nacos(
|
|
196
|
+
agent_card=agent_card,
|
|
197
|
+
a2a_transports_properties=a2a_transports_properties,
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
logger.info(
|
|
201
|
+
"[NacosRegistry] Registration task scheduled on "
|
|
202
|
+
"running event loop",
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
# No running loop: use a background thread to run asyncio.run
|
|
207
|
+
def _thread_runner():
|
|
208
|
+
try:
|
|
209
|
+
with self._registration_lock:
|
|
210
|
+
if self._shutdown_event.is_set():
|
|
211
|
+
logger.info(
|
|
212
|
+
"[NacosRegistry] Shutdown requested "
|
|
213
|
+
"before registration started",
|
|
214
|
+
)
|
|
215
|
+
self._registration_status = (
|
|
216
|
+
RegistrationStatus.CANCELLED
|
|
217
|
+
)
|
|
218
|
+
return
|
|
219
|
+
self._registration_status = (
|
|
220
|
+
RegistrationStatus.IN_PROGRESS
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
asyncio.run(
|
|
224
|
+
self._register_to_nacos(
|
|
225
|
+
agent_card=agent_card,
|
|
226
|
+
a2a_transports_properties=(
|
|
227
|
+
a2a_transports_properties
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
with self._registration_lock:
|
|
232
|
+
if (
|
|
233
|
+
self._registration_status
|
|
234
|
+
== RegistrationStatus.IN_PROGRESS
|
|
235
|
+
):
|
|
236
|
+
self._registration_status = (
|
|
237
|
+
RegistrationStatus.COMPLETED
|
|
238
|
+
)
|
|
239
|
+
except asyncio.CancelledError:
|
|
240
|
+
with self._registration_lock:
|
|
241
|
+
self._registration_status = (
|
|
242
|
+
RegistrationStatus.CANCELLED
|
|
243
|
+
)
|
|
244
|
+
logger.info("[NacosRegistry] Registration cancelled")
|
|
245
|
+
except Exception:
|
|
246
|
+
with self._registration_lock:
|
|
247
|
+
self._registration_status = RegistrationStatus.FAILED
|
|
248
|
+
logger.error(
|
|
249
|
+
"[NacosRegistry] Registration failed in "
|
|
250
|
+
"background thread",
|
|
251
|
+
exc_info=True,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
thread = threading.Thread(
|
|
255
|
+
target=_thread_runner,
|
|
256
|
+
name="nacos-registry-register",
|
|
257
|
+
daemon=True,
|
|
258
|
+
)
|
|
259
|
+
thread.start()
|
|
260
|
+
# Store thread reference after successful start for
|
|
261
|
+
# proper tracking and cleanup
|
|
262
|
+
with self._registration_lock:
|
|
263
|
+
self._register_thread = thread
|
|
264
|
+
logger.info(
|
|
265
|
+
"[NacosRegistry] Registration task started in "
|
|
266
|
+
"background thread",
|
|
267
|
+
)
|
|
268
|
+
except Exception:
|
|
269
|
+
with self._registration_lock:
|
|
270
|
+
self._registration_status = RegistrationStatus.FAILED
|
|
271
|
+
logger.warning(
|
|
272
|
+
"[NacosRegistry] Error starting registration task",
|
|
273
|
+
exc_info=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def _get_client_config(self) -> ClientConfig:
|
|
277
|
+
"""Get Nacos client configuration.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
ClientConfig: Nacos client configuration
|
|
281
|
+
"""
|
|
282
|
+
if self._nacos_client_config is not None:
|
|
283
|
+
return self._nacos_client_config
|
|
284
|
+
|
|
285
|
+
# Use centralized config builder from a2a_registry module
|
|
286
|
+
# This ensures consistent behavior with env-based registry creation
|
|
287
|
+
from .a2a_registry import (
|
|
288
|
+
get_registry_settings,
|
|
289
|
+
_build_nacos_client_config,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
settings = get_registry_settings()
|
|
293
|
+
return _build_nacos_client_config(settings)
|
|
294
|
+
|
|
295
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
296
|
+
async def _register_to_nacos(
|
|
297
|
+
self,
|
|
298
|
+
agent_card: AgentCard,
|
|
299
|
+
a2a_transports_properties: Optional[
|
|
300
|
+
List[A2ATransportsProperties]
|
|
301
|
+
] = None,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Register agent to Nacos.
|
|
304
|
+
|
|
305
|
+
Performs two-step registration:
|
|
306
|
+
1. Release agent card to Nacos
|
|
307
|
+
2. Register agent endpoint with host/port information
|
|
308
|
+
|
|
309
|
+
On successful registration, the NacosAIService connection is kept
|
|
310
|
+
alive to maintain heartbeat and health checks. The service will be
|
|
311
|
+
closed in cleanup() during application shutdown.
|
|
312
|
+
|
|
313
|
+
Most exceptions are caught and logged without re-raising to prevent
|
|
314
|
+
background tasks from crashing the host. Only `asyncio.CancelledError`
|
|
315
|
+
is re-raised.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
agent_card: The A2A agent card to register
|
|
319
|
+
a2a_transports_properties: Optional list of transport
|
|
320
|
+
configurations
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
asyncio.CancelledError: If the registration task is cancelled
|
|
324
|
+
"""
|
|
325
|
+
# Check if shutdown was requested
|
|
326
|
+
if self._shutdown_event.is_set():
|
|
327
|
+
with self._registration_lock:
|
|
328
|
+
self._registration_status = RegistrationStatus.CANCELLED
|
|
329
|
+
logger.info(
|
|
330
|
+
"[NacosRegistry] Registration cancelled due to shutdown",
|
|
331
|
+
)
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
client_config = self._get_client_config()
|
|
335
|
+
try:
|
|
336
|
+
self._nacos_ai_service = await NacosAIService.create_ai_service(
|
|
337
|
+
client_config,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Check again after service creation
|
|
341
|
+
if self._shutdown_event.is_set():
|
|
342
|
+
with self._registration_lock:
|
|
343
|
+
self._registration_status = RegistrationStatus.CANCELLED
|
|
344
|
+
logger.info(
|
|
345
|
+
"[NacosRegistry] Registration cancelled after "
|
|
346
|
+
"service creation",
|
|
347
|
+
)
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
# Publish agent card
|
|
351
|
+
await self._nacos_ai_service.release_agent_card(
|
|
352
|
+
ReleaseAgentCardParam(agent_card=agent_card),
|
|
353
|
+
)
|
|
354
|
+
logger.info(
|
|
355
|
+
"[NacosRegistry] Agent card published: agent=%s",
|
|
356
|
+
agent_card.name,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Check again before endpoint registration
|
|
360
|
+
if self._shutdown_event.is_set():
|
|
361
|
+
with self._registration_lock:
|
|
362
|
+
self._registration_status = RegistrationStatus.CANCELLED
|
|
363
|
+
logger.warning(
|
|
364
|
+
"[NacosRegistry] Registration cancelled after "
|
|
365
|
+
"card publish, endpoint may be partially "
|
|
366
|
+
"registered",
|
|
367
|
+
)
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
# Register agent endpoint
|
|
371
|
+
if a2a_transports_properties:
|
|
372
|
+
for transport in a2a_transports_properties:
|
|
373
|
+
host = transport.host
|
|
374
|
+
port = transport.port
|
|
375
|
+
endpoint_param = RegisterAgentEndpointParam(
|
|
376
|
+
agent_name=agent_card.name,
|
|
377
|
+
version=agent_card.version,
|
|
378
|
+
address=host,
|
|
379
|
+
port=port,
|
|
380
|
+
path=transport.path,
|
|
381
|
+
support_tls=transport.support_tls,
|
|
382
|
+
transport=transport.transport_type,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
await self._nacos_ai_service.register_agent_endpoint(
|
|
386
|
+
endpoint_param,
|
|
387
|
+
)
|
|
388
|
+
logger.info(
|
|
389
|
+
"[NacosRegistry] Agent endpoint registered: "
|
|
390
|
+
"agent=%s, address=%s:%s, path=%s",
|
|
391
|
+
agent_card.name,
|
|
392
|
+
host,
|
|
393
|
+
port,
|
|
394
|
+
transport.path,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
with self._registration_lock:
|
|
398
|
+
if self._registration_status == RegistrationStatus.IN_PROGRESS:
|
|
399
|
+
self._registration_status = RegistrationStatus.COMPLETED
|
|
400
|
+
# Service remains alive to maintain heartbeat and health checks.
|
|
401
|
+
# It will be closed in cleanup() during application shutdown.
|
|
402
|
+
|
|
403
|
+
except asyncio.CancelledError:
|
|
404
|
+
with self._registration_lock:
|
|
405
|
+
self._registration_status = RegistrationStatus.CANCELLED
|
|
406
|
+
logger.info("[NacosRegistry] Registration task cancelled")
|
|
407
|
+
try:
|
|
408
|
+
svc = self._nacos_ai_service
|
|
409
|
+
if svc is not None:
|
|
410
|
+
await svc.shutdown()
|
|
411
|
+
logger.debug(
|
|
412
|
+
"[NacosRegistry] NacosAIService closed due to "
|
|
413
|
+
"cancellation",
|
|
414
|
+
)
|
|
415
|
+
except Exception:
|
|
416
|
+
logger.debug(
|
|
417
|
+
"[NacosRegistry] Error closing NacosAIService on "
|
|
418
|
+
"cancellation",
|
|
419
|
+
exc_info=True,
|
|
420
|
+
)
|
|
421
|
+
raise
|
|
422
|
+
except Exception as e:
|
|
423
|
+
with self._registration_lock:
|
|
424
|
+
self._registration_status = RegistrationStatus.FAILED
|
|
425
|
+
# Log errors but don't re-raise; background tasks should
|
|
426
|
+
# not crash the host
|
|
427
|
+
logger.error(
|
|
428
|
+
"[NacosRegistry] Failed to register agent=%s: %s",
|
|
429
|
+
agent_card.name,
|
|
430
|
+
str(e),
|
|
431
|
+
exc_info=True,
|
|
432
|
+
)
|
|
433
|
+
try:
|
|
434
|
+
svc = self._nacos_ai_service
|
|
435
|
+
if svc is not None:
|
|
436
|
+
await svc.shutdown()
|
|
437
|
+
logger.debug(
|
|
438
|
+
"[NacosRegistry] NacosAIService closed due to "
|
|
439
|
+
"registration failure",
|
|
440
|
+
)
|
|
441
|
+
except Exception:
|
|
442
|
+
logger.debug(
|
|
443
|
+
"[NacosRegistry] Error closing NacosAIService on failure",
|
|
444
|
+
exc_info=True,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def get_registration_status(self) -> RegistrationStatus:
|
|
448
|
+
"""Get the current registration status.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
RegistrationStatus: Current status of the registration task
|
|
452
|
+
"""
|
|
453
|
+
with self._registration_lock:
|
|
454
|
+
return self._registration_status
|
|
455
|
+
|
|
456
|
+
async def wait_for_registration(
|
|
457
|
+
self,
|
|
458
|
+
timeout: Optional[float] = None,
|
|
459
|
+
) -> RegistrationStatus:
|
|
460
|
+
"""Wait for registration to complete, fail, or be cancelled.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
timeout: Optional timeout in seconds. If None, waits indefinitely.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
RegistrationStatus: Final status of the registration
|
|
467
|
+
"""
|
|
468
|
+
if self._register_task is not None:
|
|
469
|
+
# Task-based registration: wait for the task
|
|
470
|
+
try:
|
|
471
|
+
if timeout is not None:
|
|
472
|
+
await asyncio.wait_for(
|
|
473
|
+
self._register_task,
|
|
474
|
+
timeout=timeout,
|
|
475
|
+
)
|
|
476
|
+
else:
|
|
477
|
+
await self._register_task
|
|
478
|
+
except asyncio.TimeoutError:
|
|
479
|
+
logger.warning(
|
|
480
|
+
"[NacosRegistry] Wait for registration timed "
|
|
481
|
+
"out after %s seconds",
|
|
482
|
+
timeout,
|
|
483
|
+
)
|
|
484
|
+
except (asyncio.CancelledError, Exception):
|
|
485
|
+
pass # Task may have been cancelled or failed
|
|
486
|
+
elif self._register_thread is not None:
|
|
487
|
+
# Thread-based registration: wait for the thread (matching
|
|
488
|
+
# LocalDeployManager pattern)
|
|
489
|
+
self._register_thread.join(timeout=timeout)
|
|
490
|
+
if self._register_thread.is_alive():
|
|
491
|
+
logger.warning(
|
|
492
|
+
"[NacosRegistry] Registration thread did not "
|
|
493
|
+
"complete within timeout",
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
return self.get_registration_status()
|
|
497
|
+
|
|
498
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
499
|
+
async def cleanup(
|
|
500
|
+
self,
|
|
501
|
+
wait_for_completion: bool = True,
|
|
502
|
+
timeout: Optional[float] = 5.0,
|
|
503
|
+
) -> None:
|
|
504
|
+
"""Clean up registration resources and cancel ongoing
|
|
505
|
+
registration if needed.
|
|
506
|
+
|
|
507
|
+
This method should be called during shutdown to ensure proper cleanup.
|
|
508
|
+
It will:
|
|
509
|
+
1. Signal shutdown to prevent new registration attempts
|
|
510
|
+
2. Optionally wait for the registration to complete or timeout
|
|
511
|
+
3. Cancel the asyncio task if running
|
|
512
|
+
4. Wait for background thread if running
|
|
513
|
+
5. Clean up service connections
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
wait_for_completion: If True, wait for registration to complete
|
|
517
|
+
before cancelling. If False, cancel immediately.
|
|
518
|
+
timeout: Maximum time to wait for completion (if
|
|
519
|
+
wait_for_completion=True). Default is 5 seconds.
|
|
520
|
+
"""
|
|
521
|
+
logger.info("[NacosRegistry] Starting cleanup")
|
|
522
|
+
|
|
523
|
+
# Signal shutdown first to prevent new registration attempts
|
|
524
|
+
self._shutdown_event.set()
|
|
525
|
+
|
|
526
|
+
with self._registration_lock:
|
|
527
|
+
current_status = self._registration_status
|
|
528
|
+
|
|
529
|
+
# If registration is in progress or pending, handle cancellation
|
|
530
|
+
if current_status in (
|
|
531
|
+
RegistrationStatus.IN_PROGRESS,
|
|
532
|
+
RegistrationStatus.PENDING,
|
|
533
|
+
):
|
|
534
|
+
if (
|
|
535
|
+
wait_for_completion
|
|
536
|
+
and current_status == RegistrationStatus.IN_PROGRESS
|
|
537
|
+
):
|
|
538
|
+
logger.info(
|
|
539
|
+
"[NacosRegistry] Waiting for registration to "
|
|
540
|
+
"complete (timeout=%s)",
|
|
541
|
+
timeout,
|
|
542
|
+
)
|
|
543
|
+
try:
|
|
544
|
+
await self.wait_for_registration(timeout=timeout)
|
|
545
|
+
current_status = self.get_registration_status()
|
|
546
|
+
if current_status == RegistrationStatus.COMPLETED:
|
|
547
|
+
logger.info(
|
|
548
|
+
"[NacosRegistry] Registration completed "
|
|
549
|
+
"before shutdown",
|
|
550
|
+
)
|
|
551
|
+
elif current_status == RegistrationStatus.FAILED:
|
|
552
|
+
logger.warning(
|
|
553
|
+
"[NacosRegistry] Registration failed "
|
|
554
|
+
"before shutdown",
|
|
555
|
+
)
|
|
556
|
+
elif current_status == RegistrationStatus.CANCELLED:
|
|
557
|
+
logger.info(
|
|
558
|
+
"[NacosRegistry] Registration was cancelled",
|
|
559
|
+
)
|
|
560
|
+
except Exception as e:
|
|
561
|
+
logger.warning(
|
|
562
|
+
"[NacosRegistry] Error waiting for registration: %s",
|
|
563
|
+
e,
|
|
564
|
+
exc_info=True,
|
|
565
|
+
)
|
|
566
|
+
else:
|
|
567
|
+
if current_status == RegistrationStatus.PENDING:
|
|
568
|
+
logger.info(
|
|
569
|
+
"[NacosRegistry] Registration was pending, "
|
|
570
|
+
"marking as cancelled",
|
|
571
|
+
)
|
|
572
|
+
with self._registration_lock:
|
|
573
|
+
self._registration_status = (
|
|
574
|
+
RegistrationStatus.CANCELLED
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
logger.info(
|
|
578
|
+
"[NacosRegistry] Skipping wait, cancelling "
|
|
579
|
+
"immediately",
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Cancel the task if it's still running (matching
|
|
583
|
+
# env_service pattern)
|
|
584
|
+
if (
|
|
585
|
+
self._register_task is not None
|
|
586
|
+
and not self._register_task.done()
|
|
587
|
+
):
|
|
588
|
+
logger.info("[NacosRegistry] Cancelling registration task")
|
|
589
|
+
self._register_task.cancel()
|
|
590
|
+
try:
|
|
591
|
+
await self._register_task
|
|
592
|
+
except asyncio.CancelledError:
|
|
593
|
+
# Expected when task is cancelled: await on a
|
|
594
|
+
# cancelled task raises CancelledError
|
|
595
|
+
pass
|
|
596
|
+
except Exception as e:
|
|
597
|
+
logger.debug(
|
|
598
|
+
"[NacosRegistry] Error cancelling task: %s",
|
|
599
|
+
e,
|
|
600
|
+
exc_info=True,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Wait for thread if it's still running (matching
|
|
604
|
+
# LocalDeployManager pattern)
|
|
605
|
+
if (
|
|
606
|
+
self._register_thread is not None
|
|
607
|
+
and self._register_thread.is_alive()
|
|
608
|
+
):
|
|
609
|
+
logger.info(
|
|
610
|
+
"[NacosRegistry] Waiting for registration thread "
|
|
611
|
+
"to finish",
|
|
612
|
+
)
|
|
613
|
+
thread_timeout = (
|
|
614
|
+
timeout
|
|
615
|
+
if (
|
|
616
|
+
wait_for_completion
|
|
617
|
+
and current_status == RegistrationStatus.IN_PROGRESS
|
|
618
|
+
)
|
|
619
|
+
else 1.0
|
|
620
|
+
)
|
|
621
|
+
self._register_thread.join(timeout=thread_timeout)
|
|
622
|
+
if self._register_thread.is_alive():
|
|
623
|
+
logger.warning(
|
|
624
|
+
"[NacosRegistry] Registration thread did not "
|
|
625
|
+
"terminate, potential resource leak",
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# Always clean up service connection, regardless of status
|
|
629
|
+
if self._nacos_ai_service is not None:
|
|
630
|
+
try:
|
|
631
|
+
await self._nacos_ai_service.shutdown()
|
|
632
|
+
except Exception:
|
|
633
|
+
logger.debug(
|
|
634
|
+
"[NacosRegistry] Error closing service during cleanup",
|
|
635
|
+
exc_info=True,
|
|
636
|
+
)
|
|
637
|
+
finally:
|
|
638
|
+
self._nacos_ai_service = None
|
|
639
|
+
|
|
640
|
+
logger.info("[NacosRegistry] Cleanup completed")
|
|
@@ -144,6 +144,7 @@ class KubernetesDeployManager(DeployManager):
|
|
|
144
144
|
image_tag: str = "latest",
|
|
145
145
|
push_to_registry: bool = False,
|
|
146
146
|
use_cache: bool = True,
|
|
147
|
+
pypi_mirror: Optional[str] = None,
|
|
147
148
|
**kwargs,
|
|
148
149
|
) -> Dict[str, Any]:
|
|
149
150
|
"""
|
|
@@ -170,6 +171,7 @@ class KubernetesDeployManager(DeployManager):
|
|
|
170
171
|
mount_dir: Mount directory
|
|
171
172
|
runtime_config: K8s runtime configuration
|
|
172
173
|
use_cache: Enable build cache (default: True)
|
|
174
|
+
pypi_mirror: PyPI mirror URL for pip package installation
|
|
173
175
|
# Backward compatibility
|
|
174
176
|
image_name: Image name
|
|
175
177
|
image_tag: Image tag
|
|
@@ -209,6 +211,7 @@ class KubernetesDeployManager(DeployManager):
|
|
|
209
211
|
protocol_adapters=protocol_adapters,
|
|
210
212
|
custom_endpoints=custom_endpoints,
|
|
211
213
|
use_cache=use_cache,
|
|
214
|
+
pypi_mirror=pypi_mirror,
|
|
212
215
|
**kwargs,
|
|
213
216
|
)
|
|
214
217
|
if not built_image_name:
|
|
@@ -22,6 +22,7 @@ class DockerfileConfig(BaseModel):
|
|
|
22
22
|
health_check_endpoint: str = "/health"
|
|
23
23
|
custom_template: Optional[str] = None
|
|
24
24
|
platform: Optional[str] = None
|
|
25
|
+
pypi_mirror: Optional[str] = None
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class DockerfileGenerator:
|
|
@@ -73,8 +74,7 @@ COPY . {working_dir}/
|
|
|
73
74
|
# Install Python dependencies
|
|
74
75
|
RUN pip install --no-cache-dir --upgrade pip
|
|
75
76
|
RUN if [ -f requirements.txt ]; then \\
|
|
76
|
-
pip install --no-cache-dir -r requirements.txt
|
|
77
|
-
-i https://pypi.tuna.tsinghua.edu.cn/simple; fi
|
|
77
|
+
pip install --no-cache-dir -r requirements.txt{pypi_mirror_flag}; fi
|
|
78
78
|
|
|
79
79
|
# Create non-root user for security
|
|
80
80
|
RUN adduser --disabled-password --gecos '' {user} && \\
|
|
@@ -136,6 +136,11 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
|
136
136
|
f'"--port", "{config.port}"]'
|
|
137
137
|
)
|
|
138
138
|
|
|
139
|
+
# Prepare PyPI mirror flag
|
|
140
|
+
pypi_mirror_flag = ""
|
|
141
|
+
if config.pypi_mirror:
|
|
142
|
+
pypi_mirror_flag = f" -i {config.pypi_mirror}"
|
|
143
|
+
|
|
139
144
|
# Format template with configuration values
|
|
140
145
|
content = template.format(
|
|
141
146
|
base_image=config.base_image,
|
|
@@ -147,6 +152,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
|
147
152
|
env_vars_section=env_vars_section,
|
|
148
153
|
startup_command_section=startup_command_section,
|
|
149
154
|
platform=config.platform,
|
|
155
|
+
pypi_mirror_flag=pypi_mirror_flag,
|
|
150
156
|
)
|
|
151
157
|
|
|
152
158
|
return content
|