polaris-python 0.0.1.dev3__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.
Files changed (84) hide show
  1. polaris/__init__.py +74 -0
  2. polaris/_version.py +16 -0
  3. polaris/api/__init__.py +55 -0
  4. polaris/api/_log_file.py +232 -0
  5. polaris/api/config.py +145 -0
  6. polaris/api/consumer.py +240 -0
  7. polaris/api/log.py +414 -0
  8. polaris/api/lossless.py +73 -0
  9. polaris/api/provider.py +152 -0
  10. polaris/api/skill.py +162 -0
  11. polaris/pkg/__init__.py +15 -0
  12. polaris/pkg/common/__init__.py +15 -0
  13. polaris/pkg/common/plugin.py +33 -0
  14. polaris/pkg/config/__init__.py +15 -0
  15. polaris/pkg/config/api.py +1205 -0
  16. polaris/pkg/config/default.py +92 -0
  17. polaris/pkg/config/event.py +76 -0
  18. polaris/pkg/config/loader.py +392 -0
  19. polaris/pkg/config/stat_reporter.py +100 -0
  20. polaris/pkg/flow/__init__.py +15 -0
  21. polaris/pkg/flow/admin_server.py +104 -0
  22. polaris/pkg/flow/async_flow.py +55 -0
  23. polaris/pkg/flow/base_flow.py +111 -0
  24. polaris/pkg/flow/impl.py +1277 -0
  25. polaris/pkg/flow/lossless.py +605 -0
  26. polaris/pkg/flow/notify.py +46 -0
  27. polaris/pkg/flow/register.py +210 -0
  28. polaris/pkg/flow/skill.py +110 -0
  29. polaris/pkg/flow/sync_flow.py +20 -0
  30. polaris/pkg/model/__init__.py +15 -0
  31. polaris/pkg/model/context.py +53 -0
  32. polaris/pkg/model/contract.py +89 -0
  33. polaris/pkg/model/core.py +51 -0
  34. polaris/pkg/model/engine.py +83 -0
  35. polaris/pkg/model/error.py +94 -0
  36. polaris/pkg/model/event.py +159 -0
  37. polaris/pkg/model/lossless.py +102 -0
  38. polaris/pkg/model/rule.py +152 -0
  39. polaris/pkg/model/server.py +288 -0
  40. polaris/pkg/model/service.py +820 -0
  41. polaris/pkg/model/skill.py +179 -0
  42. polaris/pkg/model/stat.py +168 -0
  43. polaris/pkg/model/util.py +58 -0
  44. polaris/pkg/version/__init__.py +18 -0
  45. polaris/plugin/__init__.py +32 -0
  46. polaris/plugin/circuit_breaker/__init__.py +19 -0
  47. polaris/plugin/circuit_breaker/base.py +54 -0
  48. polaris/plugin/circuit_breaker/composite.py +265 -0
  49. polaris/plugin/circuit_breaker/config.py +36 -0
  50. polaris/plugin/event/__init__.py +21 -0
  51. polaris/plugin/event/pushgateway.py +202 -0
  52. polaris/plugin/load_balancer/__init__.py +24 -0
  53. polaris/plugin/load_balancer/base.py +30 -0
  54. polaris/plugin/load_balancer/random.py +39 -0
  55. polaris/plugin/load_balancer/weighted_random.py +50 -0
  56. polaris/plugin/local_registry/__init__.py +18 -0
  57. polaris/plugin/local_registry/base.py +47 -0
  58. polaris/plugin/local_registry/file_cache.py +335 -0
  59. polaris/plugin/local_registry/local_cache.py +433 -0
  60. polaris/plugin/server_connector/__init__.py +20 -0
  61. polaris/plugin/server_connector/base.py +104 -0
  62. polaris/plugin/server_connector/convert.py +149 -0
  63. polaris/plugin/server_connector/grpc_connector/__init__.py +21 -0
  64. polaris/plugin/server_connector/grpc_connector/connection.py +401 -0
  65. polaris/plugin/server_connector/grpc_connector/grpc_client.py +384 -0
  66. polaris/plugin/server_connector/grpc_connector/tls.py +45 -0
  67. polaris/plugin/service_router/__init__.py +28 -0
  68. polaris/plugin/service_router/base.py +105 -0
  69. polaris/plugin/service_router/isolated.py +35 -0
  70. polaris/plugin/service_router/metadata.py +84 -0
  71. polaris/plugin/service_router/recover.py +62 -0
  72. polaris/plugin/service_router/util.py +54 -0
  73. polaris/plugin/skill/__init__.py +16 -0
  74. polaris/plugin/skill/connector.py +127 -0
  75. polaris/plugin/skill/persistence.py +94 -0
  76. polaris/plugin/stat_reporter/__init__.py +16 -0
  77. polaris/plugin/stat_reporter/base.py +34 -0
  78. polaris/plugin/stat_reporter/prometheus.py +375 -0
  79. polaris/plugin_manager.py +146 -0
  80. polaris_python-0.0.1.dev3.dist-info/METADATA +213 -0
  81. polaris_python-0.0.1.dev3.dist-info/RECORD +84 -0
  82. polaris_python-0.0.1.dev3.dist-info/WHEEL +5 -0
  83. polaris_python-0.0.1.dev3.dist-info/licenses/LICENSE +18 -0
  84. polaris_python-0.0.1.dev3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1277 @@
1
+ # Tencent is pleased to support the open source community by making polaris-python available.
2
+ #
3
+ # Copyright (C) 2021 Tencent. All rights reserved.
4
+ #
5
+ # Licensed under the BSD 3-Clause License (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://opensource.org/licenses/BSD-3-Clause
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software distributed
12
+ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13
+ # CONDITIONS OF ANY KIND, either express or implied. See the License for the
14
+ # specific language governing permissions and limitations under the License.
15
+
16
+ import asyncio
17
+ import copy
18
+ import threading
19
+ import time
20
+ import traceback
21
+ from collections.abc import Coroutine
22
+ from typing import Any, TypeVar
23
+
24
+ from polarismesh_specification.api.v1.service_manage import response_pb2
25
+
26
+ import polaris.api.log as logger
27
+ from polaris.pkg.config.api import ClusterService, get_server_services
28
+ from polaris.pkg.flow.base_flow import CacheFilter
29
+ from polaris.pkg.flow.base_flow import CacheResult
30
+ from polaris.pkg.flow.base_flow import RealTimeLimitTask
31
+ from polaris.pkg.flow.base_flow import get_load_cache_values
32
+ from polaris.pkg.flow.admin_server import AdminHttpServer
33
+ from polaris.pkg.flow.lossless import LosslessManager, lossless_http_handlers
34
+ from polaris.pkg.flow.register import RegisterStateManager
35
+ from polaris.pkg.model.context import *
36
+ from polaris.pkg.model.core import *
37
+ from polaris.pkg.model.event import SDKEventName, SDKEventType, SDKEventStatus, now_event
38
+ from polaris.pkg.model.lossless import LosslessInstance
39
+ from polaris.pkg.model.engine import BaseEngine
40
+ from polaris.pkg.model.rule import *
41
+ from polaris.pkg.model.server import *
42
+ from polaris.pkg.model.service import *
43
+ from polaris.pkg.model import util
44
+ from polaris.pkg.version import version
45
+ from polaris.plugin.circuit_breaker.composite import CircuitBreakResult
46
+ from polaris.plugin.local_registry.file_cache import LocalFileCache
47
+ from polaris.plugin.local_registry.local_cache import InstanceFilter
48
+ from polaris.plugin.local_registry.local_cache import InstanceProperties
49
+ from polaris.plugin.local_registry.local_cache import ServiceUpdateRequest
50
+ from polaris.plugin.circuit_breaker.base import BaseCircuitBreaker
51
+ from polaris.plugin.load_balancer.base import BaseLoadBalancer
52
+ from polaris.plugin.local_registry.base import BaseLocalRegistry
53
+ from polaris.plugin.server_connector.base import BaseServerConnector
54
+ from polaris.plugin.service_router.base import BaseServiceRouter
55
+ from polaris.plugin.service_router.base import NearbyKeyAreaWithCampus
56
+ from polaris.plugin.service_router.base import NearbyKeyAreaWithRegion
57
+ from polaris.plugin.service_router.base import NearbyKeyAreaWithZone
58
+ from polaris.plugin.service_router.base import RouteInfo
59
+ from polaris.plugin_manager import Configuration
60
+ from polaris.plugin_manager import InitContext
61
+ from polaris.plugin_manager import PluginManager
62
+ from polaris.plugin_manager import PluginType
63
+ from polaris.plugin_manager import setup
64
+ from .notify import *
65
+ from polaris.pkg.model.error import ErrorCode, SDKError, ServerCode
66
+
67
+ # 定时任务时间10s
68
+ UPDATE_INTERVAL = 10
69
+ T = TypeVar("T")
70
+ network_logger = logger.get_logger("network")
71
+ cache_logger = logger.get_logger("cache")
72
+ circuitbreaker_logger = logger.get_logger("circuitbreaker")
73
+ route_logger = logger.get_logger("route")
74
+ stat_report_logger = logger.get_logger("statReport")
75
+
76
+
77
+ # 保存服务上一次的检测周期的map
78
+ keyLastCircuitBreakCheckTimes = "lastCircuitBreakCheckTimes"
79
+ # 保存健康探测上一次周期的map
80
+ keyLastClientReportTimes = "keyLastClientReportTimes"
81
+ # 保存上一次获取discover服务的时间
82
+ keyLastDiscoverServiceCheckTimes = "lastDiscoverServiceCheckTimes"
83
+
84
+
85
+ class ServiceUpdateTask:
86
+ def __init__(self, service_key: ServiceEventKey, cache_obj, update_interval: int, last_update_time: int):
87
+ self.service_key = service_key
88
+ self.update_interval = update_interval
89
+ self.last_update_time = last_update_time
90
+ self.cache_obj = cache_obj
91
+ self.runner: asyncio.Task | None = None
92
+
93
+ def need_update(self) -> bool:
94
+ cur_time = int(time.time() * 1000)
95
+ return cur_time >= self.last_update_time + self.update_interval
96
+
97
+
98
+ class FutureRequest:
99
+ def __init__(self, request: GetOneInstanceRequest, hash_value: int, do_load_balance: bool):
100
+ self.request = request
101
+ self.hash_value = hash_value
102
+ self.do_load_balance = do_load_balance
103
+
104
+
105
+ class AsyncFlowFuture:
106
+ def __init__(self, request: FutureRequest, filter_request: CacheFilter = None, redirect_times: int = 0,
107
+ engine: BaseEngine = None):
108
+ self.request = request
109
+ self.filter_request = filter_request
110
+ self.redirect_times = redirect_times
111
+ self.engine = engine
112
+
113
+
114
+ class CheckPeriods:
115
+ def __init__(self, circuit_breaker: int = 0, client_report: int = 0,
116
+ discover_service: int = 0):
117
+ self.circuit_breaker = circuit_breaker
118
+ self.client_report = client_report
119
+ self.discover_service = discover_service
120
+
121
+
122
+ class Engine(BaseEngine):
123
+ def __init__(self, config: Configuration = None, connector: BaseServerConnector = None,
124
+ registry: BaseLocalRegistry = None,
125
+ filter_only_router: BaseServiceRouter = None,
126
+ circuit_breaker_chain: Dict[str, BaseCircuitBreaker] = None, load_balancer: BaseLoadBalancer = None,
127
+ max_retry_times: int = 0,
128
+ router_chain: List[BaseServiceRouter] = None, check_periods: CheckPeriods = None,
129
+ before_router_chain: List[BaseServiceRouter] = None,
130
+ after_router_chain: List[BaseServiceRouter] = None,
131
+ default_api_timeout: int = 0,
132
+ svc_load_balancer_chain: Dict[str, BaseLoadBalancer] = None,
133
+ server_service: List[ClusterService] = None,
134
+ svc_to_router_chain: Dict[str, List[BaseServiceRouter]] = None, global_ctx: ValueContext = None,
135
+ file_cache: LocalFileCache = None):
136
+ self.thread: threading.Thread | None = None
137
+ self.loop: asyncio.AbstractEventLoop | None = None
138
+ self._loop_ready = threading.Event()
139
+ self._destroyed = False
140
+ self._destroy_callbacks = []
141
+ self._resources_released = False
142
+ provider_config = config.get_provider() if config is not None else None
143
+ min_register_interval = provider_config.get_min_register_interval() if provider_config else 30000
144
+ self.register_states = RegisterStateManager(
145
+ self._do_register, self._async_heartbeat, min_register_interval,
146
+ )
147
+ self.lossless_manager = LosslessManager(config, None) if config is not None else None
148
+ self._lossless_owned_providers = {}
149
+ self.admin_server = AdminHttpServer(
150
+ config.get_global().get_admin()) if config is not None else None
151
+ # 全局配置
152
+ self.configuration = config
153
+ # 服务端连接器
154
+ self.svc_connector = connector
155
+ # 服务本地缓存
156
+ self.registry = registry
157
+ # 只做过滤的服务器路由插件
158
+ self.filter_only_router = filter_only_router
159
+ # list
160
+ if circuit_breaker_chain is None:
161
+ self.circuit_breaker_chain = {}
162
+ else:
163
+ self.circuit_breaker_chain = circuit_breaker_chain
164
+ # 负载均衡器
165
+ self.load_balancer = load_balancer
166
+ # 最大重试次数
167
+ self.max_retry_times = max_retry_times
168
+ # 定期检查
169
+ if not check_periods:
170
+ self.check_periods = CheckPeriods()
171
+ else:
172
+ self.check_periods = check_periods
173
+ # 默认API超时时间
174
+ self.default_api_timeout = default_api_timeout
175
+ # 服务器级的均衡器
176
+ if svc_load_balancer_chain is None:
177
+ self.svc_load_balancer_chain = {}
178
+ else:
179
+ self.svc_load_balancer_chain = svc_load_balancer_chain
180
+ # 系统服务列表
181
+ if server_service is None:
182
+ self.server_services = []
183
+ else:
184
+ self.server_services = server_service
185
+ # 服务级的路由责任链
186
+ if svc_to_router_chain is None:
187
+ self.svc_to_router_chain = {}
188
+ else:
189
+ self.svc_to_router_chain = svc_to_router_chain
190
+ # 服务路由责任链
191
+ if router_chain is None:
192
+ self.router_chain = []
193
+ else:
194
+ self.router_chain = router_chain
195
+ # Java 对齐的三段路由链
196
+ self.before_router_chain = [] if before_router_chain is None else before_router_chain
197
+ self.after_router_chain = [] if after_router_chain is None else after_router_chain
198
+ self.global_ctx = global_ctx if global_ctx is not None else ValueContext()
199
+ self.file_cache = file_cache
200
+
201
+ # 全局更新任务集
202
+ self.update_task_set = {}
203
+ self._circuit_breaker_timers = {}
204
+ self.skill_flow = None
205
+ self.skill_connector = None
206
+ self.stat_reporter = None
207
+ self.event_reporters = []
208
+
209
+ def set_default_value_for_instances_request(self, req: GetInstancesRequest):
210
+ if req.timeout == 0:
211
+ req.set_timeout(self.default_api_timeout)
212
+ if req.retry_count == 0:
213
+ req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
214
+ return req
215
+
216
+ def set_deafult_value_for_reqisgter_request(self, req: InstanceRegisterRequest):
217
+ if req.timeout == 0:
218
+ req.set_timeout(self.default_api_timeout)
219
+ if req.retry_count == 0:
220
+ req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
221
+ return req
222
+
223
+ def set_default_value_for_deregister_request(self, req: InstanceDeRegisterRequest):
224
+ if req.timeout == 0:
225
+ req.set_timeout(self.default_api_timeout)
226
+ if req.retry_count == 0:
227
+ req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
228
+ return req
229
+
230
+ def set_default_value_for_heartbeat_request(self, req: InstanceHeartbeatRequest):
231
+ if req.timeout == 0:
232
+ req.set_timeout(self.default_api_timeout)
233
+ if req.retry_count == 0:
234
+ req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
235
+ return req
236
+
237
+ def set_default_value_for_rule_request(self, req: GetServiceRuleRequest):
238
+ if req.timeout == 0:
239
+ req.set_timeout(self.default_api_timeout)
240
+ if req.retry_count == 0:
241
+ req.set_retry_count(self.configuration.get_global().get_api().get_max_retry_times())
242
+ return req
243
+
244
+ def get_router_chain(self, svc: ServiceKey):
245
+ chain = self.svc_to_router_chain.get(svc, None)
246
+ if chain:
247
+ return chain
248
+ else:
249
+ return self.router_chain
250
+
251
+ def get_full_router_chain(self, svc: ServiceKey):
252
+ return self.before_router_chain + self.get_router_chain(svc) + self.after_router_chain
253
+
254
+ # 获取负载均衡器
255
+ def get_load_balancer(self, svc: ServiceKey):
256
+ return self.svc_load_balancer_chain.get(svc, self.load_balancer)
257
+
258
+ # 初始化服务端插件
259
+ def init_server_service_plugins(self, plugins: PluginManager):
260
+ router_list = list()
261
+ for router_name in DefaultConf.DefaultServerServiceRouterChain:
262
+ router = plugins.get_instance(PluginType.SERVICE_ROUTER, router_name)
263
+ router_list.append(router)
264
+
265
+ for svc in self.server_services:
266
+ self.svc_to_router_chain[svc.service_key] = router_list
267
+ load_balance = plugins.get_instance(PluginType.LOAD_BALANCE,
268
+ DefaultConf.DefaultServerServiceToLoadBalancer[svc.cluster_type])
269
+ self.svc_load_balancer_chain[svc.service_key] = load_balance
270
+ return True
271
+
272
+ def instance_request_to_cache_filter(self, req: GetInstancesRequest, redirect_service: ServiceMetadata):
273
+ filters = CacheFilter(namespace=req.get_namespace(), service=req.get_service(),
274
+ dest_instance_fileter=InstanceFilter(service=req.get_service(),
275
+ namespace=req.get_namespace(),
276
+ is_internal_request=False))
277
+ if redirect_service:
278
+ filters = build_redirected_filter(filters, redirect_service)
279
+ return filters
280
+
281
+ def create_async_flow_future(self, req: FutureRequest):
282
+ future = AsyncFlowFuture(request=req)
283
+ future.engine = Engine
284
+ return future
285
+
286
+ def start_job_thread(self):
287
+ """Start the single event loop used by gRPC and background jobs."""
288
+ if self.thread is not None and self.thread.is_alive():
289
+ logger.warning("sdk has already had a alive thread")
290
+ return
291
+
292
+ startup_errors = []
293
+
294
+ def run_jobs():
295
+ loop = asyncio.new_event_loop()
296
+ asyncio.set_event_loop(loop)
297
+ self.loop = loop
298
+ try:
299
+ loop.run_until_complete(self.init_self_ip())
300
+ if self.stat_reporter is not None:
301
+ loop.run_until_complete(self.stat_reporter.start(self))
302
+ if self.admin_server is not None:
303
+ loop.run_until_complete(self.admin_server.start())
304
+ for event_reporter in self.event_reporters:
305
+ loop.run_until_complete(event_reporter.start(self))
306
+ logger.info("start timing update job")
307
+ loop.create_task(self.timing_update_server_task())
308
+ loop.create_task(self.timing_expire_cache_task())
309
+ if self.circuit_breaker_chain:
310
+ logger.info("start circuit break job")
311
+ loop.create_task(self.timing_check_circuit_break_task())
312
+ logger.info("start job of client reporting")
313
+ loop.create_task(self.add_client_report_task())
314
+ if hasattr(self.svc_connector, "start_address_switching"):
315
+ self.svc_connector.start_address_switching()
316
+ self._loop_ready.set()
317
+ loop.run_forever()
318
+ except Exception as error:
319
+ startup_errors.append(error)
320
+ finally:
321
+ if startup_errors:
322
+ self._destroyed = True
323
+ loop.run_until_complete(self._shutdown())
324
+ pending = [task for task in asyncio.all_tasks(loop) if not task.done()]
325
+ for task in pending:
326
+ task.cancel()
327
+ if pending:
328
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
329
+ loop.close()
330
+ self.loop = None
331
+ self._finish_destroy()
332
+ self._loop_ready.set()
333
+
334
+ def async_job_threading():
335
+ with logger.use_context_logger(self.global_ctx.logger):
336
+ run_jobs()
337
+
338
+ self.thread = threading.Thread(target=async_job_threading, daemon=True)
339
+ self.thread.start()
340
+ connect_timeout = self.configuration.get_global().get_server_connector().get_connect_timeout() / 1000
341
+ if not self._loop_ready.wait(timeout=max(5, connect_timeout + 5)):
342
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "failed to start SDK event loop")
343
+ if startup_errors:
344
+ raise startup_errors[0]
345
+
346
+ async def init_self_ip(self):
347
+ """Resolve this context's SDK IP before reporting without changing instance hosts."""
348
+ global_config = self.configuration.get_global()
349
+ api_config = global_config.get_api()
350
+ if api_config.bind_ip or api_config.get_bind_intf():
351
+ return
352
+ api_config.bind_ip_value = ""
353
+ connector = global_config.get_server_connector()
354
+ address = connector.get_addresses()[0]
355
+ try:
356
+ api_config.bind_ip_value = await util.get_local_ip(address, connector.get_connect_timeout())
357
+ except (OSError, ValueError) as error:
358
+ network_logger.warning("failed to resolve SDK bindIP via %s; keeping it empty: %s", address, error)
359
+
360
+ def add_destroy_callback(self, callback):
361
+ """Release context-owned resources only after this engine has stopped."""
362
+ self._destroy_callbacks.append(callback)
363
+
364
+ def _finish_destroy(self):
365
+ if self._resources_released:
366
+ return
367
+ self._resources_released = True
368
+ if self.skill_flow is not None:
369
+ try:
370
+ self.skill_flow.destroy()
371
+ except Exception:
372
+ cache_logger.error("failed to release Skill backups: %s", traceback.format_exc())
373
+ self.update_task_set.clear()
374
+ for callback in self._destroy_callbacks:
375
+ try:
376
+ callback()
377
+ except Exception:
378
+ with logger.use_context_logger(self.global_ctx.logger):
379
+ logger.error("failed to release context resources: %s", traceback.format_exc())
380
+ self._destroy_callbacks.clear()
381
+
382
+ def _submit(self, coroutine, loop):
383
+ # Bind the task at submission without wrapping an unstarted coroutine.
384
+ with logger.use_context_logger(self.global_ctx.logger):
385
+ return asyncio.run_coroutine_threadsafe(coroutine, loop)
386
+
387
+ async def _run_async(self, coroutine: Coroutine[Any, Any, T]) -> T:
388
+ """Run a coroutine on the SDK loop from any caller loop."""
389
+ loop = self.loop
390
+ if self._destroyed or loop is None:
391
+ coroutine.close()
392
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
393
+ try:
394
+ running_loop = asyncio.get_running_loop()
395
+ except RuntimeError:
396
+ running_loop = None
397
+ if running_loop is loop:
398
+ with logger.use_context_logger(self.global_ctx.logger):
399
+ return await coroutine
400
+ try:
401
+ future = self._submit(coroutine, loop)
402
+ except RuntimeError as error:
403
+ coroutine.close()
404
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK event loop is unavailable") from error
405
+ return await asyncio.wrap_future(future)
406
+
407
+ def _run_sync(self, coroutine: Coroutine[Any, Any, T]) -> T:
408
+ """Block on a coroutine running on the SDK loop."""
409
+ loop = self.loop
410
+ if self._destroyed or loop is None:
411
+ coroutine.close()
412
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
413
+ if self.thread is not None and threading.current_thread() is self.thread:
414
+ coroutine.close()
415
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError,
416
+ "sync API cannot run on the SDK event loop; use the async_ API")
417
+ try:
418
+ asyncio.get_running_loop()
419
+ except RuntimeError:
420
+ pass
421
+ else:
422
+ coroutine.close()
423
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError,
424
+ "sync API cannot run in an active event loop; use the async_ API")
425
+ try:
426
+ future = self._submit(coroutine, loop)
427
+ except RuntimeError as error:
428
+ coroutine.close()
429
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK event loop is unavailable") from error
430
+ return future.result()
431
+
432
+ async def _shutdown(self):
433
+ current = asyncio.current_task()
434
+ if self.lossless_manager is not None:
435
+ await self.lossless_manager.close()
436
+ for event_reporter in self.event_reporters:
437
+ await event_reporter.close()
438
+ self.event_reporters = []
439
+ pending = [task for task in asyncio.all_tasks() if task is not current and not task.done()]
440
+ for task in pending:
441
+ task.cancel()
442
+ if pending:
443
+ await asyncio.gather(*pending, return_exceptions=True)
444
+ for handle, _, _ in self._circuit_breaker_timers.values():
445
+ handle.cancel()
446
+ self._circuit_breaker_timers.clear()
447
+ await self.register_states.close()
448
+ if self.stat_reporter is not None:
449
+ await self.stat_reporter.close()
450
+ if self.admin_server is not None:
451
+ await self.admin_server.close()
452
+ close = getattr(self.svc_connector, "close", None)
453
+ if close is not None:
454
+ await close()
455
+ if self.skill_connector is not None:
456
+ await self.skill_connector.close()
457
+
458
+ async def _shutdown_on_loop(self, loop):
459
+ try:
460
+ await self._shutdown()
461
+ finally:
462
+ loop.stop()
463
+
464
+ def destroy(self):
465
+ """Stop jobs and close the selected server connector."""
466
+ if self._destroyed:
467
+ return
468
+ self._destroyed = True
469
+ loop = self.loop
470
+ if loop is not None and loop.is_running():
471
+ if threading.current_thread() is self.thread:
472
+ loop.create_task(self._shutdown_on_loop(loop))
473
+ return
474
+ try:
475
+ self._submit(self._shutdown(), loop).result()
476
+ except Exception:
477
+ with logger.use_context_logger(self.global_ctx.logger):
478
+ network_logger.error("failed to close server connector: %s", traceback.format_exc())
479
+ finally:
480
+ if not loop.is_closed():
481
+ loop.call_soon_threadsafe(loop.stop)
482
+ if self.thread is not None and self.thread is not threading.current_thread():
483
+ self.thread.join()
484
+ if self.thread is None or not self.thread.is_alive():
485
+ self.loop = None
486
+ self._finish_destroy()
487
+
488
+ def sync_get_instances(self, req: GetInstancesRequest, do_balance: bool = True, load: bool = True):
489
+ """Return candidates through the SDK event loop."""
490
+ return self._run_sync(self._query_instances(req, "candidates", load))
491
+
492
+ async def async_get_instances(self, req: GetInstancesRequest, do_balance: bool = True,
493
+ load: bool = True) -> CacheResult:
494
+ """Asynchronously load all instances through the SDK event loop."""
495
+ return await self._run_async(self._query_instances(req, "candidates", load))
496
+
497
+ def sync_get_all_instances(self, req):
498
+ return self._run_sync(self._query_instances(req, "all"))
499
+
500
+ async def async_get_all_instances(self, req):
501
+ return await self._run_async(self._query_instances(req, "all"))
502
+
503
+ def sync_get_healthy_instances(self, req):
504
+ return self._run_sync(self._query_instances(req, "healthy"))
505
+
506
+ async def async_get_healthy_instances(self, req):
507
+ return await self._run_async(self._query_instances(req, "healthy"))
508
+
509
+ def sync_get_services(self):
510
+ """Return service resources currently held by this context."""
511
+ return self._run_sync(self._async_get_services())
512
+
513
+ async def async_get_services(self):
514
+ """Return service resources currently held by this context."""
515
+ return await self._run_async(self._async_get_services())
516
+
517
+ async def _async_get_services(self):
518
+ return tuple(self.registry.get_services())
519
+
520
+ async def _query_instances(self, req, mode, load=True):
521
+ from polaris.pkg.model.service import InstancesResponse
522
+ result = await self._async_get_instances(req, load=load)
523
+ if result is None or result.dest_instances is None:
524
+ return None
525
+ snapshot = result.dest_instances
526
+ if mode != "all":
527
+ snapshot = self.filter_instances(req, snapshot, healthy_only=mode == "healthy")
528
+ return InstancesResponse(snapshot, req.flow_id)
529
+
530
+ async def _async_get_instances(self, req: GetInstancesRequest, do_balance: bool = True,
531
+ load: bool = True) -> CacheResult:
532
+ req = self.set_default_value_for_instances_request(req)
533
+ if self._warmup_enabled():
534
+ self.lossless_manager.touch_rules(req.get_namespace(), req.get_service())
535
+ request = self.instance_request_to_cache_filter(req=req, redirect_service=None)
536
+ result = await get_load_cache_values(self.registry, request, load, req.use_discover_cache, req.timeout)
537
+ return result
538
+
539
+ def sync_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
540
+ """Load one instance through the SDK event loop."""
541
+ return self._run_sync(self._async_get_one_instance(req))
542
+
543
+ async def async_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
544
+ """Asynchronously load one instance through the SDK event loop."""
545
+ return await self._run_async(self._async_get_one_instance(req))
546
+
547
+ async def _async_get_one_instance(self, req: GetOneInstanceRequest) -> Instance:
548
+ instance_request = GetInstancesRequest(flow_id=req.flow_id, service=req.service, namespace=req.namespace,
549
+ timeout=req.timeout, use_discover_cache=req.use_discover_cache,
550
+ exclude_offline_service=req.exclude_offline_service)
551
+
552
+ cache_result = await self._async_get_instances(instance_request)
553
+
554
+ if cache_result is None:
555
+ return None
556
+
557
+ return self.choose_instance(req, cache_result.dest_instances,
558
+ cache_result.source_route_rule, cache_result.dest_route_rule)
559
+
560
+ def choose_instance(self, req, instances, source_route_rule=None, dest_route_rule=None):
561
+ """Apply the configured router chain and load balancer to an instance snapshot."""
562
+
563
+ service_instances = self.filter_instances(req, instances)
564
+ if self._warmup_enabled():
565
+ service_instances = self.lossless_manager.apply_warmup_weights(service_instances)
566
+ result = self.load_balancer.choose_instance(service_instances)
567
+ if result is not None and self.circuit_breaker_chain:
568
+ status = result.get_circuit_breaker_status()
569
+ if status.get_status() == CBStatusEnum.HalfOpen and not status.try_allocate_request():
570
+ candidates = [node for node in service_instances.instances
571
+ if node is not result and node.is_healthy() and
572
+ node.get_circuit_breaker_status().is_available()]
573
+ while candidates:
574
+ service_instances.instances = candidates
575
+ selected = self.load_balancer.choose_instance(service_instances)
576
+ if selected is None:
577
+ break
578
+ if selected.get_circuit_breaker_status().try_allocate_request():
579
+ return selected
580
+ candidates = [node for node in candidates if node is not selected]
581
+ route_logger.warning("all instances unavailable; returning %s:%d without half-open permit",
582
+ result.get_host(), result.get_port())
583
+ if result is not None:
584
+ route_logger.debug(
585
+ "load balancer selected %s:%d namespace=%s service=%s",
586
+ result.get_host(), result.get_port(), req.namespace, req.service,
587
+ )
588
+ return result
589
+
590
+ def _warmup_enabled(self) -> bool:
591
+ if self.lossless_manager is None:
592
+ return False
593
+ weight_adjust = self.configuration.get_consumer().get_weight_adjust()
594
+ return weight_adjust.is_enable() and "warmup" in weight_adjust.get_chain()
595
+
596
+ def filter_instances(self, req, instances, healthy_only=False):
597
+ """Filter a copied list without balancing or allocating half-open permits."""
598
+
599
+ service_instances = copy.copy(instances)
600
+ if getattr(req, "exclude_offline_service", False):
601
+ if service_instances.get_service_status() == ServiceStatus.OFFLINE:
602
+ service_instances.instances = []
603
+ service_instances.total_weight = 0
604
+ return service_instances
605
+
606
+ routeInfo = RouteInfo(source_service=req.source_service,
607
+ dest_service=req,
608
+ include_unhealthy_instances=getattr(req, "include_unhealthy_instance", False),
609
+ include_circuitbreak_instances=getattr(req, "include_circuit_break_instances", False),
610
+ canary=req.canary,
611
+ metadata=req.get_metadata(),
612
+ metadata_failover_type=req.get_metadata_failover_type())
613
+ for item in self.get_full_router_chain(ServiceKey(req.namespace, req.service)):
614
+ if (healthy_only or getattr(req, "skip_route_filter", False)) and item in self.router_chain:
615
+ continue
616
+ if not item.enable(routeInfo, service_instances):
617
+ route_logger.debug(
618
+ "router %s skipped namespace=%s service=%s",
619
+ type(item).__name__, req.namespace, req.service,
620
+ )
621
+ continue
622
+ before = len(service_instances.instances)
623
+ filter_result = item.get_filtered_instances(route_info=routeInfo, instances=service_instances)
624
+ if filter_result is None:
625
+ return None
626
+ service_instances.instances = filter_result.instances
627
+ route_logger.debug(
628
+ "router %s namespace=%s service=%s instances=%d->%d",
629
+ type(item).__name__, req.namespace, req.service,
630
+ before, len(service_instances.instances),
631
+ )
632
+ if not service_instances.instances:
633
+ break
634
+ return service_instances
635
+
636
+ async def _skill_call(self, operation, request):
637
+ if self.skill_flow is None:
638
+ from polaris.pkg.flow.skill import SkillFlow
639
+ self.skill_flow = SkillFlow(
640
+ self.skill_connector, self.configuration.get_ai().get_skill().get_server_connector(),
641
+ self.global_ctx.logger.get_logger("cache"),
642
+ )
643
+ return await getattr(self.skill_flow, operation)(request)
644
+
645
+ def sync_skill_call(self, operation, request):
646
+ """Run a blocking Skill operation on the context's owner loop."""
647
+ return self._run_sync(self._skill_call(operation, request))
648
+
649
+ async def async_skill_call(self, operation, request):
650
+ """Run a Skill operation on the context's owner loop."""
651
+ return await self._run_async(self._skill_call(operation, request))
652
+
653
+ def sync_contract_call(self, operation, request):
654
+ """Run a blocking service-contract operation on the context's owner loop."""
655
+ return self._run_sync(getattr(self.svc_connector, operation)(request))
656
+
657
+ async def async_contract_call(self, operation, request):
658
+ """Run a service-contract operation on the context's owner loop."""
659
+ return await self._run_async(getattr(self.svc_connector, operation)(request))
660
+
661
+ def sync_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
662
+ """Register an instance on the SDK event loop."""
663
+ return self._run_sync(self._async_register(req))
664
+
665
+ async def async_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
666
+ """Asynchronously register through the same instance lifecycle flow."""
667
+ return await self._run_async(self._async_register(req))
668
+
669
+ async def _do_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
670
+ return await self.svc_connector.register_instance(req)
671
+
672
+ async def _async_register(self, req: InstanceRegisterRequest) -> InstanceRegisterResponse:
673
+ if req.auto_heartbeat and req.ttl is None:
674
+ req.ttl = DefaultHeartbeatTTL
675
+ req.validate()
676
+ snapshot = copy.deepcopy(req)
677
+ async with self.register_states.operation_lock(snapshot):
678
+ response = await self._do_register(snapshot)
679
+ if self._destroyed:
680
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "SDK engine has been destroyed")
681
+ req.instance_id = snapshot.instance_id = response.instance_id
682
+ if snapshot.auto_heartbeat:
683
+ await self.register_states.put(snapshot)
684
+ return response
685
+
686
+ def sync_deregister(self, req: InstanceDeRegisterRequest) -> None:
687
+ """Deregister an instance on the SDK event loop."""
688
+ return self._run_sync(self._async_deregister(req))
689
+
690
+ async def async_deregister(self, req: InstanceDeRegisterRequest) -> None:
691
+ """Asynchronously stop instance tasks and deregister through the SDK loop."""
692
+ return await self._run_async(self._async_deregister(req))
693
+
694
+ async def _async_deregister(self, req: InstanceDeRegisterRequest) -> None:
695
+ async with self.register_states.operation_lock(req):
696
+ state_request = await self.register_states.remove(req)
697
+ outgoing = copy.deepcopy(req)
698
+ if state_request is not None:
699
+ outgoing.instance_id = state_request.instance_id
700
+ await self.svc_connector.deregister_instance(outgoing)
701
+ event_center = self.global_ctx.core_map.get(ContextKeyEventCenter)
702
+ if event_center is not None:
703
+ event_center.publish(now_event(
704
+ self.global_ctx.get_client_id(), SDKEventType.INSTANCE, SDKEventName.INSTANCE_THREAD_END,
705
+ client_ip=self.configuration.get_global().get_api().get_bind_ip(),
706
+ namespace=outgoing.namespace, service=outgoing.service,
707
+ instance_id=outgoing.instance_id, host=outgoing.host, port=outgoing.port,
708
+ ))
709
+
710
+ def sync_lossless_register(self, req: InstanceRegisterRequest) -> None:
711
+ """Run lossless online for one instance on the SDK event loop."""
712
+ return self._run_sync(self._async_lossless_register(req))
713
+
714
+ async def async_lossless_register(self, req: InstanceRegisterRequest) -> None:
715
+ """Asynchronously run lossless online through the SDK loop."""
716
+ return await self._run_async(self._async_lossless_register(req))
717
+
718
+ async def _async_lossless_register(self, req: InstanceRegisterRequest) -> None:
719
+ instance = LosslessInstance.from_register_request(req)
720
+ self._ensure_lossless_provider(req, instance)
721
+ await self.lossless_manager.lossless_register(instance)
722
+
723
+ def sync_lossless_deregister(self, req) -> None:
724
+ """Run lossless offline for one instance on the SDK event loop."""
725
+ return self._run_sync(self._async_lossless_deregister(req))
726
+
727
+ async def async_lossless_deregister(self, req) -> None:
728
+ """Asynchronously run lossless offline through the SDK loop."""
729
+ return await self._run_async(self._async_lossless_deregister(req))
730
+
731
+ async def _async_lossless_deregister(self, req) -> None:
732
+ instance = self._lossless_instance_for(req)
733
+ if self.lossless_manager.get_action_provider(instance) is None:
734
+ self._ensure_lossless_provider(req, instance)
735
+ await self.lossless_manager.lossless_deregister(instance)
736
+
737
+ @staticmethod
738
+ def _lossless_instance_for(req) -> LosslessInstance:
739
+ metadata = dict(getattr(req, "metadata", {}) or {})
740
+ return LosslessInstance(
741
+ namespace=req.namespace, service=req.service,
742
+ host=req.host, port=req.port, metadata=metadata,
743
+ )
744
+
745
+ def _ensure_lossless_provider(self, req: InstanceRegisterRequest, instance: LosslessInstance) -> None:
746
+ from polaris.pkg.flow.lossless import HttpLosslessActionProvider
747
+
748
+ async def register_action(_properties):
749
+ await self._async_register(req)
750
+
751
+ async def deregister_action():
752
+ deregister = InstanceDeRegisterRequest(
753
+ service=req.service, service_token=req.service_token, namespace=req.namespace,
754
+ host=req.host, port=req.port, instance_id=getattr(req, "instance_id", ""),
755
+ timeout=self.default_api_timeout,
756
+ retry_count=self.configuration.get_global().get_api().get_max_retry_times(),
757
+ )
758
+ await self._async_deregister(deregister)
759
+
760
+ manager = self.lossless_manager
761
+ existing = manager.get_action_provider(instance)
762
+ if existing is not None and self._lossless_owned_providers.get(instance) is not existing:
763
+ return
764
+ provider = HttpLosslessActionProvider(
765
+ instance, register_action, deregister_action,
766
+ strategy_resolver=lambda: manager.cached_options(instance).strategy,
767
+ health_check_path_resolver=lambda: manager.cached_options(instance).health_check_path,
768
+ )
769
+ manager.set_action_provider(instance, provider)
770
+ self._lossless_owned_providers[instance] = provider
771
+
772
+ def sync_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
773
+ """Send an instance heartbeat on the SDK event loop."""
774
+ return self._run_sync(self._async_heartbeat(req))
775
+
776
+ async def async_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
777
+ """Asynchronously send an explicit heartbeat through the SDK loop."""
778
+ return await self._run_async(self._async_heartbeat(req))
779
+
780
+ async def _async_heartbeat(self, req: InstanceHeartbeatRequest) -> None:
781
+ return await self.svc_connector.heartbeat(req)
782
+
783
+ def update_location(self, region: str, zone: str, campus: str):
784
+ self.global_ctx.set_value(NearbyKeyAreaWithCampus, campus)
785
+ self.global_ctx.set_value(NearbyKeyAreaWithRegion, region)
786
+ self.global_ctx.set_value(NearbyKeyAreaWithZone, zone)
787
+
788
+ def do_circuit_break_for_service(self, svc: ServiceKey, svc_instances: ServiceInstances, cb_name: str = ""):
789
+ """
790
+ 对服务进行熔断判断操作
791
+ :param cb_name:
792
+ :param svc:
793
+ :param svc_instances:
794
+ :return:
795
+ """
796
+
797
+ request = ServiceUpdateRequest()
798
+ request.namespace = svc_instances.get_namespace()
799
+ request.service = svc_instances.get_service()
800
+ checked = []
801
+ transitions = []
802
+ for name, cb in self.circuit_breaker_chain.items():
803
+ if cb_name and name != cb_name:
804
+ continue
805
+ result = cb.timing_circuit_break(svc_instances.instances)
806
+ for target, nodes in ((CBStatusEnum.Open, result.instance_2_open),
807
+ (CBStatusEnum.Closed, result.instance_2_close),
808
+ (CBStatusEnum.HalfOpen, result.instance_2_halfopen)):
809
+ for node in nodes:
810
+ previous_status = node.get_circuit_breaker_status().get_status()
811
+ properties = InstanceProperties()
812
+ properties.service = svc
813
+ properties.ID = node.get_id()
814
+ allowance = cb.get_half_open_request_count() if target == CBStatusEnum.HalfOpen else 0
815
+ properties.properties = {"CircuitBreakerStatus": CircuitBreakerStatus(
816
+ status=target, ts=result.now, allowed_requests_after_half_open=allowance)}
817
+ request.properties.append(properties)
818
+ transitions.append((node, target, previous_status))
819
+ cb.on_state_change(node)
820
+ checked.append((name, cb))
821
+ if request.properties:
822
+ self.registry.update_instances(request)
823
+ event_center = self.global_ctx.core_map.get(ContextKeyEventCenter)
824
+ if event_center is not None:
825
+ status_names = {
826
+ CBStatusEnum.Open: SDKEventStatus.OPEN,
827
+ CBStatusEnum.HalfOpen: SDKEventStatus.HALF_OPEN,
828
+ CBStatusEnum.Closed: SDKEventStatus.CLOSE,
829
+ }
830
+ event_names = {
831
+ CBStatusEnum.Open: SDKEventName.CIRCUIT_BREAKER_OPEN,
832
+ CBStatusEnum.HalfOpen: SDKEventName.CIRCUIT_BREAKER_HALF_OPEN,
833
+ CBStatusEnum.Closed: SDKEventName.CIRCUIT_BREAKER_CLOSE,
834
+ }
835
+ for node, target, previous_status in transitions:
836
+ host = node.get_host()
837
+ port = node.get_port()
838
+ event_center.publish(now_event(
839
+ self.global_ctx.get_client_id(), SDKEventType.CIRCUIT_BREAKING, event_names[target],
840
+ client_ip=self.configuration.get_global().get_api().get_bind_ip(),
841
+ namespace=node.get_namespace(), service=node.get_service(),
842
+ instance_id=node.get_id(), host=host, port=port,
843
+ previous_status=status_names.get(previous_status, ""), current_status=status_names[target],
844
+ rule_name="", additional={"isolation_object": f"{host}:{port}"},
845
+ ))
846
+ for name, cb in checked:
847
+ for node in svc_instances.instances:
848
+ self._schedule_circuit_break_check(node, name, cb)
849
+
850
+ def _schedule_circuit_break_check(self, instance, cb_name, cb):
851
+ if self.loop is None or self._destroyed:
852
+ return
853
+ key = (instance.get_namespace(), instance.get_service(), instance.get_id(), cb_name)
854
+ due = cb.next_check_time(instance)
855
+ existing = self._circuit_breaker_timers.get(key)
856
+ if existing is not None:
857
+ handle, local_value, scheduled_due = existing
858
+ if (due is not None and due == scheduled_due and local_value is instance.get_local_value()
859
+ and not handle.cancelled()):
860
+ return
861
+ handle.cancel()
862
+ self._circuit_breaker_timers.pop(key, None)
863
+ if due is None:
864
+ return
865
+ local_value = instance.get_local_value()
866
+
867
+ def check():
868
+ self._circuit_breaker_timers.pop(key, None)
869
+ if self._destroyed:
870
+ return
871
+ try:
872
+ cached = self.registry.get_instances(InstanceFilter(
873
+ namespace=key[0], service=key[1], is_internal_request=True))
874
+ if cached is None or cached.get_instance_local_value(key[2]) is not local_value:
875
+ return
876
+ self.do_circuit_break_for_service(ServiceKey(key[0], key[1]), cached, cb_name)
877
+ except Exception:
878
+ circuitbreaker_logger.error("failed to check instance circuit breaker: %s", traceback.format_exc())
879
+
880
+ handle = self.loop.call_later(max(0, due - cb.now()) / 1000, check)
881
+ self._circuit_breaker_timers[key] = (handle, local_value, due)
882
+
883
+ def _prune_circuit_breaker_timers(self, service_key, instances=None):
884
+ for key, (handle, local_value, _) in list(self._circuit_breaker_timers.items()):
885
+ if key[:2] != (service_key.namespace, service_key.service):
886
+ continue
887
+ if instances is not None and instances.get_instance_local_value(key[2]) is local_value:
888
+ continue
889
+ handle.cancel()
890
+ self._circuit_breaker_timers.pop(key, None)
891
+
892
+ async def process_real_time_task(self, task_data: RealTimeLimitTask):
893
+ service_key = task_data.get_service_key()
894
+ svr_instances = self.registry.get_instances(
895
+ InstanceFilter(service=service_key.get_service(), namespace=service_key.get_namespace(),
896
+ is_internal_request=True))
897
+ if svr_instances is None or not svr_instances.is_initialized():
898
+ return None
899
+ self.do_circuit_break_for_service(task_data.get_service_key(), svr_instances, task_data.get_cb_name())
900
+ # await self.do_period_task(task_data.get_service_key(), svr_instances)
901
+ return None
902
+
903
+ @staticmethod
904
+ def get_now_ts():
905
+ return int(time.time() * 1000)
906
+
907
+ # 获取服务规则
908
+ async def sync_get_service_rule(self, event_type: int, req: GetServiceRuleRequest) -> ServiceRuleResponse:
909
+ """Compatibility entry used by the asynchronous public API."""
910
+ return await self._run_async(self._get_service_rule(event_type, req))
911
+
912
+ async def _get_service_rule(self, event_type: int, req: GetServiceRuleRequest) -> ServiceRuleResponse:
913
+ """Reject non-instance resources until those caches are implemented."""
914
+ del event_type, req
915
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "only instance resources are cached")
916
+
917
+ def sync_update_service_call_result(self, req: ServiceCallResult):
918
+ return self._run_sync(self._update_service_call_result(req))
919
+
920
+ async def async_update_service_call_result(self, req: ServiceCallResult):
921
+ return await self._run_async(self._update_service_call_result(req))
922
+
923
+ async def _update_service_call_result(self, req: ServiceCallResult):
924
+ if self.stat_reporter is not None:
925
+ self.stat_reporter.report(req)
926
+ if not self.circuit_breaker_chain:
927
+ return None
928
+ if req.called_instance is None or req.ret_code is None:
929
+ return None
930
+ svc = ServiceKey(req.get_namespace(), req.get_service())
931
+ cached = self.registry.get_instances(InstanceFilter(
932
+ namespace=svc.namespace, service=svc.service, is_internal_request=True))
933
+ if cached is None or cached.get_instance_local_value(req.get_id()) is not req.called_instance.get_local_value():
934
+ return None
935
+ instance = cached.instances_map[req.get_id()]
936
+ for cb_name, cb in self.circuit_breaker_chain.items():
937
+ ret = cb.realtime_circuit_break(instance, req.get_ret_code(), req.get_delay())
938
+ if ret:
939
+ self.do_circuit_break_for_service(svc, cached, cb_name)
940
+ else:
941
+ self._schedule_circuit_break_check(instance, cb_name, cb)
942
+
943
+ async def timing_update_server_task(self):
944
+ while True:
945
+ try:
946
+ network_logger.debug("begin to update server info")
947
+ if not self.update_task_set:
948
+ continue
949
+ local_update_task_set = self.update_task_set.copy()
950
+ for service_key, update_task in local_update_task_set.items():
951
+ if update_task.cache_obj.loading or not update_task.need_update():
952
+ continue
953
+ if update_task.runner is None or update_task.runner.done():
954
+ update_task.runner = asyncio.create_task(self._run_service_update_task(update_task))
955
+ except Exception as exception:
956
+ network_logger.error("receive exception when doing periodic server info updating %s", traceback.format_exc())
957
+ finally:
958
+ await asyncio.sleep(self.configuration.get_consumer().local_cache.service_refresh_interval / 1000)
959
+
960
+ async def _run_service_update_task(self, task):
961
+ try:
962
+ await self.process_update_task(task)
963
+ except Exception:
964
+ cache_logger.error("failed to refresh resource %s: %s", task.service_key.__dict__, traceback.format_exc())
965
+
966
+ async def timing_expire_cache_task(self):
967
+ """Periodically reclaim idle resources independently of pending discovery reads."""
968
+ expire_time = self.configuration.get_consumer().get_local_cache().get_service_expire_time()
969
+ check_interval = min(expire_time / 2, 60 * 60 * 1000) / 1000
970
+ while True:
971
+ await asyncio.sleep(check_interval)
972
+ try:
973
+ await self.registry.expire_services()
974
+ except Exception:
975
+ cache_logger.error("failed to evict idle cache resources: %s", traceback.format_exc())
976
+
977
+ async def stop_service_update(self, service_key, cache_obj):
978
+ """Stop the old resource generation before closing its discovery stream."""
979
+ if service_key.event_type == EventType.Instance:
980
+ self._prune_circuit_breaker_timers(service_key)
981
+ task = self.update_task_set.get(service_key)
982
+ if task is not None and task.cache_obj is cache_obj:
983
+ self.update_task_set.pop(service_key)
984
+ if task.runner is not None and task.runner is not asyncio.current_task() and not task.runner.done():
985
+ task.runner.cancel()
986
+ await asyncio.gather(task.runner, return_exceptions=True)
987
+ request = InstanceDiscoverRequest(1, service_key.service, service_key.namespace)
988
+ try:
989
+ await self.svc_connector.stop_discover(request)
990
+ finally:
991
+ await self.file_cache.delete(request)
992
+
993
+ def _service_update_active(self, task):
994
+ return self.update_task_set.get(task.service_key) is task and not task.cache_obj.invalid
995
+
996
+ async def timing_check_circuit_break_task(self):
997
+ while True:
998
+ try:
999
+ circuitbreaker_logger.debug("begin to do circuit break task")
1000
+ services = self.registry.get_services()
1001
+ for service_key, v in services.items():
1002
+ if service_key.event_type != EventType.Instance:
1003
+ continue
1004
+ svr_instances = self.registry.get_instances(
1005
+ InstanceFilter(service=service_key.get_service(), namespace=service_key.get_namespace(),
1006
+ is_internal_request=True))
1007
+ if svr_instances is None or not svr_instances.is_initialized():
1008
+ continue
1009
+ self.do_circuit_break_for_service(service_key, svr_instances)
1010
+ except Exception as e:
1011
+ circuitbreaker_logger.error("receive exception when doing circuit break checking %s", traceback.format_exc())
1012
+ finally:
1013
+ await asyncio.sleep(self.configuration.get_consumer().circuit_breaker.check_period / 1000)
1014
+
1015
+ async def process_update_task(self, task: ServiceUpdateTask):
1016
+ if not self._service_update_active(task) or task.cache_obj.loading or not task.need_update():
1017
+ return
1018
+
1019
+ revision = self.registry.get_service_revision(task.service_key.service, task.service_key.namespace,
1020
+ task.service_key.event_type)
1021
+ if task.service_key.event_type != EventType.Instance:
1022
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
1023
+ "only instance resources are cached")
1024
+ request = InstanceDiscoverRequest(type=1, name=task.service_key.service,
1025
+ namespace=task.service_key.namespace, revision=revision)
1026
+
1027
+ client = self.svc_connector
1028
+
1029
+ def get_local_value_func(ins):
1030
+ cached = task.cache_obj.value
1031
+ local_value = cached.get_instance_local_value(ins.get_id()) if cached is not None else None
1032
+ if local_value is None:
1033
+ return DefaultInstanceLocalValue()
1034
+ else:
1035
+ return local_value
1036
+
1037
+ for _ in range(self.configuration.get_global().get_api().max_retry_times):
1038
+ if not self._service_update_active(task):
1039
+ return
1040
+ try:
1041
+ ret = await client.discover(request)
1042
+ if not self._service_update_active(task):
1043
+ return
1044
+
1045
+ service_ins = ServiceInstances.new_from_discover_rsp(ret, get_local_value_func)
1046
+ if (task.cache_obj.value is not None and not task.cache_obj.value.loaded_from_file
1047
+ and task.cache_obj.value.revision == service_ins.revision):
1048
+ task.last_update_time = int(time.time() * 1000)
1049
+ break
1050
+ self._prune_circuit_breaker_timers(task.service_key, service_ins)
1051
+ cache_logger.debug("get instance update %s %s %s", task.service_key.__dict__,
1052
+ len(service_ins.instances),
1053
+ service_ins.filtered_instances.__dict__)
1054
+
1055
+ # for k, v in self.registry.service_map.items():
1056
+ # print(k.service, v.value.instances)
1057
+ update_ret = self.registry.update_service(task.service_key, service_ins)
1058
+ if update_ret is False:
1059
+ cache_logger.error("Failed to update service %s", task.service_key.__dict__)
1060
+ break
1061
+ self.file_cache.save(request, ret)
1062
+ task.last_update_time = int(time.time() * 1000)
1063
+
1064
+ except SDKError as err:
1065
+ if not self._service_update_active(task):
1066
+ return
1067
+ if err.server_code == ServerCode.NotFoundService:
1068
+ async with task.cache_obj.load_lock:
1069
+ await self.registry.remove_service(task.service_key, task.cache_obj)
1070
+ return
1071
+ if err.err_code == ErrorCode.ErrCodeDataNotChange:
1072
+ task.cache_obj.remote_updated = True
1073
+ task.last_update_time = int(time.time() * 1000)
1074
+ cache_logger.debug("Data not change for updating %s", task.service_key.__dict__)
1075
+ break
1076
+ cache_logger.error("Failed to discover %s in periodic updating task because %s", task.service_key.__dict__,
1077
+ repr(err))
1078
+ continue
1079
+ else:
1080
+ cache_logger.info(
1081
+ "cache snapshot updated namespace=%s service=%s revision=%s instances=%d",
1082
+ task.service_key.namespace, task.service_key.service,
1083
+ service_ins.revision, len(service_ins.instances),
1084
+ )
1085
+ break
1086
+ return
1087
+
1088
+ def load_local_client_report_result(self):
1089
+ resp = response_pb2.Response()
1090
+ # TODO load from local cache file
1091
+
1092
+ async def add_client_report_task(self):
1093
+ values = ValueContext()
1094
+ while True:
1095
+ try:
1096
+ await self.do_period_client_report(values)
1097
+ except Exception as exception:
1098
+ stat_report_logger.error("receive exception when add client report task %s", traceback.format_exc())
1099
+ finally:
1100
+ await asyncio.sleep(UPDATE_INTERVAL)
1101
+
1102
+ async def do_period_client_report(self, ctx_values: ValueContext):
1103
+ now = time.time()
1104
+ if keyLastClientReportTimes in ctx_values.core_map:
1105
+ last_report_time_value = ctx_values.get_value(keyLastClientReportTimes)
1106
+ if (now - last_report_time_value) * 1000 < self.check_periods.client_report:
1107
+ return
1108
+ ctx_values.set_value(keyLastClientReportTimes, now)
1109
+ api_config = self.configuration.get_global().get_api()
1110
+ client_host = api_config.get_bind_ip()
1111
+
1112
+ report_client_req = ReportClientRequest(
1113
+ version=version,
1114
+ client_id=self.global_ctx.get_client_id(),
1115
+ timeout=self.default_api_timeout,
1116
+ # TODO
1117
+ persist_handler=None
1118
+ )
1119
+
1120
+ if len(client_host) > 0:
1121
+ report_client_req.host = client_host
1122
+
1123
+ if self.stat_reporter is not None:
1124
+ info = self.stat_reporter.meta_info()
1125
+ if info is not None:
1126
+ report_client_req.reporter_meta_infos = [info]
1127
+
1128
+ report_client_resp = await self.svc_connector.report_client(report_client_req)
1129
+
1130
+ self.update_location(
1131
+ region=report_client_resp.region,
1132
+ zone=report_client_resp.zone,
1133
+ campus=report_client_resp.campus
1134
+ )
1135
+
1136
+ def build_instance_property(now, allowed_request: int, request: ServiceUpdateRequest, cb_name: str):
1137
+ property = InstanceProperties()
1138
+ property.set_id()
1139
+
1140
+
1141
+ def build_redirected_filter(filters: CacheFilter, redirect_service: ServiceMetadata):
1142
+ filters.service = redirect_service.get_service()
1143
+ filters.namespace = redirect_service.get_namespace()
1144
+ filters.desc_instances_filter.service = redirect_service.get_service()
1145
+ filters.desc_instances_filter.namespace = redirect_service.get_namespace()
1146
+ return filters
1147
+
1148
+
1149
+ def build_service_update_request(svc: ServiceKey, results: Dict[str, CircuitBreakResult]):
1150
+ request = ServiceUpdateRequest(namespace=svc.get_namespace(), service=svc.get_service())
1151
+ for cb_name, result in results.items():
1152
+ build_instance_property(result.instance_2_close, result)
1153
+
1154
+ return None
1155
+
1156
+
1157
+ def create_flow_engine(config: Configuration, plugins: PluginManager, global_ctx: ValueContext) -> Engine:
1158
+ if global_ctx is None:
1159
+ global_ctx = ValueContext()
1160
+
1161
+ logger.info("polaris engine initializing clientId=%s version=%s",
1162
+ global_ctx.get_client_id(), version)
1163
+ setup(InitContext(value_ctx=global_ctx, config=config, plugins=plugins))
1164
+
1165
+ flow_engine = Engine(config=config, default_api_timeout=config.get_global().get_api().get_timeout(),
1166
+ global_ctx=global_ctx, file_cache=plugins.get_instance(PluginType.LOCAL_CACHE, "file"),
1167
+ server_service=list(get_server_services(config).values()))
1168
+ try:
1169
+ return _initialize_flow_engine(flow_engine, config, plugins, global_ctx)
1170
+ except Exception:
1171
+ flow_engine.destroy()
1172
+ raise
1173
+
1174
+
1175
+ def _initialize_flow_engine(flow_engine, config, plugins, global_ctx):
1176
+ protocol = config.get_global().get_server_connector().get_protocol()
1177
+
1178
+ # 加载服务端连接器
1179
+ target_plugin = plugins.get_instance(PluginType.SERVER_CONNECTOR, protocol)
1180
+ flow_engine.svc_connector = target_plugin
1181
+ if flow_engine.lossless_manager is not None:
1182
+ flow_engine.lossless_manager._connector = target_plugin
1183
+ lossless_config = config.get_provider().get_lossless()
1184
+ admin_config = config.get_global().get_admin()
1185
+ if lossless_config.enable and admin_config.port is not None and admin_config.port >= 0:
1186
+ for method, path, handler in lossless_http_handlers(flow_engine.lossless_manager):
1187
+ flow_engine.admin_server.add_route(method, path, handler)
1188
+ flow_engine.skill_connector = plugins.get_instance(PluginType.SKILL_CONNECTOR, "polaris")
1189
+ stat_chain = config.get_global().get_stat_reporter().get_chain()
1190
+ stat_reporter_name = stat_chain[0] if stat_chain else "prometheus"
1191
+ if stat_reporter_name == "pushgateway":
1192
+ stat_reporter_name = "prometheus"
1193
+ flow_engine.stat_reporter = plugins.get_instance(PluginType.STAT_REPORTER, stat_reporter_name)
1194
+ event_reporter_config = config.get_global().get_event_reporter()
1195
+ if event_reporter_config.is_enable():
1196
+ for reporter_name in event_reporter_config.get_chain():
1197
+ flow_engine.event_reporters.append(
1198
+ plugins.get_instance(PluginType.EVENT_REPORTER, reporter_name))
1199
+ if hasattr(flow_engine.svc_connector, "connection"):
1200
+ flow_engine.svc_connector.connection.stat_reporter = flow_engine.stat_reporter
1201
+ flow_engine.svc_connector.engine = flow_engine
1202
+
1203
+ # 加载本地缓存
1204
+ local_cache_type = config.get_consumer().get_local_cache().get_type()
1205
+ target_plugin = plugins.get_instance(PluginType.LOCAL_CACHE, local_cache_type)
1206
+ flow_engine.registry = target_plugin
1207
+
1208
+ # 加载注册中心服务地址依赖插件
1209
+ ret = flow_engine.init_server_service_plugins(plugins)
1210
+ if not ret:
1211
+ # TODO
1212
+ # raise Error
1213
+ return None
1214
+
1215
+ # 加载 Java 对齐的 before / core / after 三段服务路由链
1216
+ router_config = config.get_consumer().get_service_router()
1217
+ router_names = {}
1218
+ for config_field, engine_field in (
1219
+ ("before_chain", "before_router_chain"),
1220
+ ("chain", "router_chain"),
1221
+ ("after_chain", "after_router_chain")):
1222
+ names = list(getattr(router_config, "get_%s" % config_field)() or ())
1223
+ routers = [plugins.get_instance(PluginType.SERVICE_ROUTER, router_name)
1224
+ for router_name in names]
1225
+ setattr(flow_engine, engine_field, routers)
1226
+ router_names[config_field] = names
1227
+ route_logger.info(
1228
+ "router chains initialized before=%s core=%s after=%s",
1229
+ router_names["before_chain"], router_names["chain"], router_names["after_chain"],
1230
+ )
1231
+
1232
+ # 加载负载均衡插件
1233
+ load_balance_type = config.get_consumer().get_load_balancer().get_type()
1234
+ target_plugin = plugins.get_instance(PluginType.LOAD_BALANCE, load_balance_type)
1235
+ flow_engine.load_balancer = target_plugin
1236
+
1237
+ # 加载全局上下文
1238
+ flow_engine.global_ctx = global_ctx
1239
+
1240
+ # 加载客户端本地信息
1241
+ flow_engine.load_local_client_report_result()
1242
+ # 添加客户端定期上报任务
1243
+ flow_engine.check_periods.client_report = config.get_global().get_api().get_report_interval()
1244
+
1245
+ # 加载熔断器组件
1246
+ cb_config = config.get_consumer().get_circuit_breaker()
1247
+ enable_cb = cb_config.is_enable() and cb_config.is_default_rule_enable()
1248
+ if enable_cb:
1249
+ cb_chain = config.get_consumer().get_circuit_breaker().get_chain()
1250
+ if len(cb_chain) > 0:
1251
+ for cb_name in cb_chain:
1252
+ target_plugin = plugins.get_instance(PluginType.CIRCUIT_BREAKER, cb_name)
1253
+ # print("polaris CIRCUIT_BREAKER plugin", target_plugin)
1254
+ flow_engine.circuit_breaker_chain[cb_name] = target_plugin
1255
+ circuitbreaker_logger.info(
1256
+ "circuit breaker chain initialized enabled=%s plugins=%s",
1257
+ enable_cb, list(flow_engine.circuit_breaker_chain),
1258
+ )
1259
+
1260
+ global_ctx.set_value(ContextKeyEngine, flow_engine)
1261
+ logger.info(
1262
+ "polaris engine initialized clientId=%s version=%s protocol=%s addresses=%s "
1263
+ "localCache=%s loadBalancer=%s routers=%s circuitBreakers=%s lossless=%s "
1264
+ "eventReporters=%s statReporter=%s admin=%s:%s reportInterval=%dms",
1265
+ global_ctx.get_client_id(), version, protocol,
1266
+ list(config.get_global().get_server_connector().get_addresses()),
1267
+ local_cache_type, load_balance_type, router_names,
1268
+ list(flow_engine.circuit_breaker_chain),
1269
+ flow_engine.lossless_manager.enabled,
1270
+ [type(reporter).__name__ for reporter in flow_engine.event_reporters],
1271
+ type(flow_engine.stat_reporter).__name__ if flow_engine.stat_reporter is not None else None,
1272
+ config.get_global().get_admin().host, config.get_global().get_admin().port,
1273
+ flow_engine.check_periods.client_report,
1274
+ )
1275
+ logger.info("success to start polaris engine")
1276
+ flow_engine.start_job_thread()
1277
+ return flow_engine