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,1205 @@
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 ipaddress
17
+ from typing import Dict
18
+ from typing import List
19
+
20
+ from polaris.pkg.config.default import DefaultConf
21
+ from polaris.pkg.model import util
22
+ from polaris.pkg.model.core import ServiceKey
23
+ from polaris.pkg.model.error import ErrorCode
24
+ from polaris.pkg.model.error import SDKError
25
+ from polaris.pkg.model.service import RunMode
26
+ from polaris.pkg.config.stat_reporter import AdminConfig, prometheus_config
27
+ from polaris.pkg.config.event import pushgateway_event_config
28
+
29
+
30
+ # 默认集群类型
31
+ class ClusterType:
32
+ BuiltinCluster = "builtin"
33
+ DiscoverCluster = "discover"
34
+ HealthCheckCluster = "healthCheck"
35
+ MonitorCluster = "monitor"
36
+
37
+
38
+ class SelfVerifier:
39
+ def verify(self):
40
+ pass
41
+
42
+ def set_default(self):
43
+ pass
44
+
45
+
46
+ class ClusterService:
47
+ def __init__(self, service_key: ServiceKey, cluster_type: str):
48
+ self.service_key = service_key
49
+ self.cluster_type = cluster_type
50
+
51
+
52
+ # 单个服务集群配置
53
+ class ServerClusterConfig:
54
+ def __init__(self, namespace: str | None = None, service: str | None = None,
55
+ refresh_interval: int | None = None):
56
+ # Namespace string `yaml:"namespace"`
57
+ # Service string `yaml:"service"`
58
+ # RefreshInterval *time.Duration `yaml:"refreshInterval"`
59
+ self.namespace = namespace
60
+ self.service = service
61
+ self.refresh_interval = refresh_interval
62
+
63
+ def get_namespace(self):
64
+ return self.namespace
65
+
66
+ def get_service(self):
67
+ return self.service
68
+
69
+ def get_refresh_interval(self):
70
+ return self.refresh_interval
71
+
72
+ def verify(self):
73
+ if self.refresh_interval is None or self.refresh_interval < DefaultConf.DefaultMinTimingInterval:
74
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
75
+ "ServerClusterConfig: refreshInterval can not be empty and must greater than %d" %
76
+ DefaultConf.DefaultMinTimingInterval)
77
+
78
+ def set_default(self):
79
+ if self.namespace is None:
80
+ self.namespace = ""
81
+ if self.service is None:
82
+ self.service = ""
83
+ if self.refresh_interval is None:
84
+ self.refresh_interval = DefaultConf.DefaultServerServiceRefreshInterval
85
+
86
+
87
+ def create_server_cluster_conf(service_key: ServiceKey):
88
+ return ServerClusterConfig(service_key.get_namespace(), service_key.get_service())
89
+
90
+
91
+ # 系统配置
92
+ class SystemConfig(SelfVerifier):
93
+ """System options with cluster fields matching the Java and Go SDK configuration."""
94
+
95
+ def __init__(self, mode: int = 0, discover_cluster: ServerClusterConfig = None,
96
+ health_check_cluster: ServerClusterConfig = None, monitor_cluster: ServerClusterConfig = None):
97
+ self.mode = mode
98
+ self.discover_cluster = discover_cluster
99
+ self.health_check_cluster = health_check_cluster
100
+ self.monitor_cluster = monitor_cluster
101
+
102
+ def get_mode(self) -> int:
103
+ return self.mode
104
+
105
+ def get_discover_cluster(self) -> ServerClusterConfig:
106
+ """Return the discovery server cluster configuration."""
107
+ return self.discover_cluster
108
+
109
+ def get_health_check_cluster(self) -> ServerClusterConfig:
110
+ """Return the health-check server cluster configuration."""
111
+ return self.health_check_cluster
112
+
113
+ def get_monitor_cluster(self) -> ServerClusterConfig:
114
+ """Return the monitoring server cluster configuration."""
115
+ return self.monitor_cluster
116
+
117
+ def verify(self):
118
+ if self.mode != RunMode.ModeNoAgent and self.mode != RunMode.ModeWithAgent:
119
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
120
+ "SystemConfig: global.system.mode=%d is invalid, you can use no-agent(%d) or with-agent(%d)" % (
121
+ self.mode, RunMode.ModeNoAgent, RunMode.ModeWithAgent))
122
+ self.discover_cluster.verify()
123
+ self.health_check_cluster.verify()
124
+ self.monitor_cluster.verify()
125
+
126
+ def set_default(self):
127
+ if self.discover_cluster is None:
128
+ self.discover_cluster = ServerClusterConfig()
129
+ if self.health_check_cluster is None:
130
+ self.health_check_cluster = ServerClusterConfig()
131
+ if self.monitor_cluster is None:
132
+ self.monitor_cluster = ServerClusterConfig()
133
+ self.discover_cluster.set_default()
134
+ self.health_check_cluster.set_default()
135
+ self.monitor_cluster.set_default()
136
+
137
+
138
+ # API访问相关的配置
139
+ class APIConfig(SelfVerifier):
140
+ """Configure API access and the SDK's own address, independently of instance hosts."""
141
+
142
+ def __init__(self, timeout: int | None = None, bind_intf: str = "", bind_ip: str = "",
143
+ report_interval: int | None = None,
144
+ max_retry_times: int = 0,
145
+ retry_interval: int | None = None):
146
+ # Timeout *time.Duration `yaml:"timeout"`
147
+ # BindIntf string `yaml:"bindIf"`
148
+ # BindIP string `yaml:"bindIP"`
149
+ # BindIPValue string `yaml:"_"`
150
+ # ReportInterval *time.Duration `yaml:"reportInterval"`
151
+ # MaxRetryTimes int `yaml:"maxRetryTimes"`
152
+ # RetryInterval *time.Duration `yaml:"retryInterval"`
153
+ self.timeout = timeout
154
+ self.bind_intf = bind_intf
155
+ self.bind_ip = bind_ip
156
+ self.bind_ip_value = ""
157
+ self.report_interval = report_interval
158
+ self.max_retry_times = max_retry_times
159
+ self.retry_interval = retry_interval
160
+
161
+ def get_timeout(self):
162
+ return self.timeout
163
+
164
+ def get_bind_intf(self):
165
+ return self.bind_intf
166
+
167
+ def get_bind_ip(self):
168
+ """Return the explicit or startup-resolved SDK address, not an instance host."""
169
+ return self.bind_ip_value
170
+
171
+ def get_report_interval(self):
172
+ return self.report_interval
173
+
174
+ def get_max_retry_times(self):
175
+ return self.max_retry_times
176
+
177
+ def get_retry_interval(self):
178
+ return self.retry_interval
179
+
180
+ def verify(self):
181
+ if self.max_retry_times < 0:
182
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
183
+ "APIConfig: global.api.maxRetryTimes must be greater than 0")
184
+ if not isinstance(self.bind_ip, str) or not isinstance(self.bind_intf, str):
185
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
186
+ "APIConfig: global.api.bindIP and bindIf must be strings")
187
+ self.bind_ip_value = self.bind_ip
188
+ if not self.bind_ip and self.bind_intf:
189
+ try:
190
+ self.bind_ip_value = util.get_ip(self.bind_intf)
191
+ except (OSError, ValueError) as error:
192
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
193
+ "APIConfig: can not get ip from provided bind interface %s" % self.bind_intf) from error
194
+ if self.retry_interval < DefaultConf.DefaultAPIRetryInterval:
195
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
196
+ "APIConfig: global.api.retryInterval must be greater than %d" %
197
+ DefaultConf.DefaultAPIRetryInterval)
198
+
199
+ def set_default(self):
200
+ if self.timeout is None:
201
+ self.timeout = DefaultConf.DefaultAPIInvokeTimeout
202
+ if self.report_interval is None:
203
+ self.report_interval = DefaultConf.DefaultReportClientIntervalDuration
204
+ if self.retry_interval is None:
205
+ self.retry_interval = DefaultConf.DefaultAPIRetryInterval
206
+ if not self.max_retry_times:
207
+ self.max_retry_times = DefaultConf.DefaultAPIMaxRetryTimes
208
+ self.bind_ip_value = self.bind_ip
209
+
210
+
211
+ # global.statReporter
212
+ class StatReporterConfig:
213
+ def __init__(self, enable: bool | None = None, chain: List[str] = None, plugin: Dict = None):
214
+ # //是否启动上报
215
+ # Enable *bool `yaml:"enable"`
216
+ # //上报插件链
217
+ # Chain []string `yaml:"chain"`
218
+ # // 插件相关配置
219
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
220
+ self.enable = enable
221
+ if chain is None:
222
+ self.chain = []
223
+ else:
224
+ self.chain = chain
225
+ if plugin is None:
226
+ self.plugin = {}
227
+ else:
228
+ self.plugin = plugin
229
+
230
+ def is_enable(self):
231
+ return bool(self.enable)
232
+
233
+ def get_chain(self):
234
+ return self.chain
235
+
236
+ def get_plugin_config(self, plugin_name: str, value):
237
+ return load_plugin_config("statReporter", plugin_name, self.plugin, value)
238
+
239
+ def get_prometheus(self):
240
+ """Return the typed Prometheus options from YAML or code."""
241
+ result = prometheus_config(self.plugin.get("prometheus", {}))
242
+ self.plugin["prometheus"] = result
243
+ return result
244
+
245
+ def verify(self):
246
+ if type(self.enable) is not bool or not isinstance(self.chain, list):
247
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "Invalid global.statReporter enable/chain")
248
+ if not isinstance(self.plugin, dict):
249
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "statReporter.plugin must be an object")
250
+ if self.enable and (len(self.chain) != 1 or self.chain[0] not in ("prometheus", "pushgateway")):
251
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
252
+ "global.statReporter.chain must be [prometheus] or [pushgateway]")
253
+ if self.enable and self.chain == ["pushgateway"] and self.get_prometheus().type != "push":
254
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
255
+ "statReporter chain [pushgateway] requires prometheus.type=push")
256
+ self.get_prometheus()
257
+
258
+
259
+ def set_default(self):
260
+ if self.enable is None:
261
+ self.enable = DefaultConf.DefaultStatReportEnabled
262
+ if len(self.chain) == 0:
263
+ self.chain = [DefaultConf.DefaultStatReporter]
264
+ self.get_prometheus()
265
+
266
+
267
+ class EventReporterConfig:
268
+ """Configure the context-owned event reporter chain."""
269
+
270
+ def __init__(self, enable: bool | None = None, chain: List[str] = None, plugin: Dict = None):
271
+ self.enable = enable
272
+ self.chain = [] if chain is None else chain
273
+ self.plugin = {} if plugin is None else plugin
274
+
275
+ def is_enable(self):
276
+ return bool(self.enable)
277
+
278
+ def get_chain(self):
279
+ return self.chain
280
+
281
+ def get_pushgateway(self):
282
+ """Return the typed Pushgateway event options from YAML or code."""
283
+ result = pushgateway_event_config(self.plugin.get("pushgateway", {}))
284
+ self.plugin["pushgateway"] = result
285
+ return result
286
+
287
+ def verify(self):
288
+ if type(self.enable) is not bool or not isinstance(self.chain, list):
289
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "Invalid global.eventReporter enable/chain")
290
+ if not isinstance(self.plugin, dict):
291
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "eventReporter.plugin must be an object")
292
+ if self.enable and self.chain != ["pushgateway"]:
293
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "Only the pushgateway event reporter is supported")
294
+ self.get_pushgateway()
295
+
296
+ def set_default(self):
297
+ if self.enable is None:
298
+ self.enable = False
299
+ if len(self.chain) == 0:
300
+ self.chain = ["pushgateway"]
301
+ self.get_pushgateway()
302
+
303
+
304
+ # 本地缓存配置
305
+ class LocalCacheConfig:
306
+ def __init__(self, service_expire_time: int | None = None, service_refresh_interval: int | None = None, persist_dir: str = "",
307
+ type: str = "",
308
+ persist_max_write_retry: int = 0,
309
+ persist_max_read_retry: int = 0, persist_retry_interval: int | None = None, plugin: Dict = None):
310
+ # // consumer.localCache.service.expireTime,
311
+ # // 服务的超时淘汰时间
312
+ # ServiceExpireTime *time.Duration `yaml:"serviceExpireTime"`
313
+ # // consumer.localCache.service.refreshInterval
314
+ # // 服务的定期刷新时间
315
+ # ServiceRefreshInterval *time.Duration `yaml:"serviceRefreshInterval"`
316
+ # // consumer.localCache.persistDir
317
+ # // 本地缓存持久化路径
318
+ # PersistDir string `yaml:"persistDir"`
319
+ # // consumer.localCache.type
320
+ # // 本地缓存类型,默认inmemory,可修改成具体的缓存插件名
321
+ # Type string `yaml:"type"`
322
+ # // consumer.localCache.persistMaxWriteRetry
323
+ # PersistMaxWriteRetry int `yaml:"persistMaxWriteRetry"`
324
+ # // consumer.localCache.persistReadRetry
325
+ # PersistMaxReadRetry int `yaml:"persistMaxReadRetry"`
326
+ # // consumer.localCache.persistRetryInterval
327
+ # PersistRetryInterval *time.Duration `yaml:"persistRetryInterval"`
328
+ # // 插件相关配置
329
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
330
+ self.service_expire_time = service_expire_time
331
+ self.service_refresh_interval = service_refresh_interval
332
+ self.persist_dir = persist_dir
333
+ self.type = type
334
+ self.persist_max_write_retry = persist_max_write_retry
335
+ self.persist_max_read_retry = persist_max_read_retry
336
+ self.persist_retry_interval = persist_retry_interval
337
+ if plugin is None:
338
+ self.plugin = {}
339
+ else:
340
+ self.plugin = plugin
341
+
342
+ # GetServiceExpireTime consumer.localCache.service.expireTime,
343
+ # 服务的超时淘汰时间
344
+ def get_service_expire_time(self):
345
+ return self.service_expire_time
346
+
347
+ # GetServiceRefreshInterval consumer.localCache.service.refreshInterval
348
+ # 服务的定期刷新时间
349
+ def get_service_refresh_interval(self):
350
+ return self.service_refresh_interval
351
+
352
+ # GetPersistPath consumer.localCache.persist.path
353
+ # 本地缓存持久化路径
354
+ def get_persist_dir(self):
355
+ return self.persist_dir
356
+
357
+ def get_type(self):
358
+ return self.type
359
+
360
+ def get_persist_max_write_retry(self):
361
+ return self.persist_max_write_retry
362
+
363
+ def get_persist_max_read_retry(self):
364
+ return self.persist_max_read_retry
365
+
366
+ def get_persist_retry_interval(self):
367
+ """Return the delay between disk retries in milliseconds."""
368
+ return self.persist_retry_interval
369
+
370
+ def get_plugin_config(self, plugin_name: str, value):
371
+ return load_plugin_config("localCache", plugin_name, self.plugin, value)
372
+
373
+ def verify(self):
374
+ if self.service_expire_time < DefaultConf.DefaultMinServiceExpireTime:
375
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
376
+ "LocalCacheConfig: consumer.localCache.serviceExpireTime %d is less than the minimal "
377
+ "allowed duration %d" % (self.service_expire_time, DefaultConf.DefaultMinServiceExpireTime))
378
+ for name in ("persist_max_write_retry", "persist_max_read_retry", "persist_retry_interval"):
379
+ value = getattr(self, name)
380
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
381
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
382
+ "LocalCacheConfig: %s must be non-negative integer" % name)
383
+
384
+ def set_default(self):
385
+ if self.service_expire_time is None:
386
+ self.service_expire_time = DefaultConf.DefaultServiceExpireTime
387
+ if self.service_refresh_interval is None:
388
+ self.service_refresh_interval = DefaultConf.DefaultServiceRefreshIntervalDuration
389
+ if len(self.persist_dir) == 0:
390
+ self.persist_dir = DefaultConf.DefaultCachePersistDir
391
+ if len(self.type) == 0:
392
+ self.type = DefaultConf.DefaultLocalCache
393
+ if self.persist_retry_interval is None:
394
+ self.persist_retry_interval = DefaultConf.DefaultPersistRetryInterval
395
+ if self.persist_max_read_retry == 0:
396
+ self.persist_max_read_retry = DefaultConf.DefaultPersistMaxReadRetry
397
+ if self.persist_max_write_retry == 0:
398
+ self.persist_max_write_retry = DefaultConf.DefaultPersistMaxWriteRetry
399
+
400
+
401
+ # 服务路由配置
402
+ class ServiceRouterConfig:
403
+ def __init__(self, before_chain: List[str] = None, chain: List[str] = None,
404
+ after_chain: List[str] = None, plugin: Dict = None, percent_of_min_instances: float = 0):
405
+ # //服务路由责任链
406
+ # Chain []string `yaml:"chain"`
407
+ # // 插件相关配置
408
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
409
+ # // 进行过滤时的最大过滤比例
410
+ # PercentOfMinInstances *float64 `yaml:"percentOfMinInstances"`
411
+ self.before_chain = [] if before_chain is None else before_chain
412
+ self.chain = [] if chain is None else chain
413
+ self.after_chain = [] if after_chain is None else after_chain
414
+ self.percent_of_min_instances = percent_of_min_instances
415
+ if plugin is None:
416
+ self.plugin = {}
417
+ else:
418
+ self.plugin = plugin
419
+
420
+ def get_chain(self):
421
+ return self.chain
422
+
423
+ def get_before_chain(self):
424
+ return self.before_chain
425
+
426
+ def get_after_chain(self):
427
+ return self.after_chain
428
+
429
+ def get_percent_of_min_instances(self):
430
+ return self.percent_of_min_instances
431
+
432
+ def get_plugin_config(self, plugin_name: str, value):
433
+ return load_plugin_config("serviceRouter", plugin_name, self.plugin, value)
434
+
435
+ def verify(self):
436
+ if self.percent_of_min_instances >= 1 or self.percent_of_min_instances < 0:
437
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
438
+ "ServiceRouterConfig: consumer.servicerouter.percentOfMinInstances "
439
+ "must be in range [0.0, 1.0)")
440
+ for name in ("before_chain", "chain", "after_chain"):
441
+ value = getattr(self, name)
442
+ if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
443
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
444
+ "ServiceRouterConfig: %s must be a list of router names" % name)
445
+ if len(self.before_chain) == 0 or len(self.after_chain) == 0:
446
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
447
+ "ServiceRouterConfig: beforeChain and afterChain cannot be empty")
448
+
449
+ def set_default(self):
450
+ if len(self.before_chain) == 0:
451
+ self.before_chain = [DefaultConf.DefaultServiceRouterIsolated]
452
+ if len(self.chain) == 0:
453
+ self.chain = [DefaultConf.DefaultServiceRouterMetadata]
454
+ if len(self.after_chain) == 0:
455
+ self.after_chain = [DefaultConf.DefaultServiceRouterRecover]
456
+ if not self.percent_of_min_instances:
457
+ self.percent_of_min_instances = DefaultConf.DefaultPercentOfMinInstances
458
+
459
+
460
+ # 通用加载插件配置的方法,主要是进行一次序列化和反序列化的过程
461
+ # 一般只会在插件的Init过程调用
462
+ def load_plugin_config(typ: str, plugin_name: str, configs: Dict, value):
463
+ if configs is None:
464
+ return None
465
+
466
+ if plugin_name not in configs:
467
+ return None
468
+
469
+ config_value = configs[plugin_name]
470
+ # TODO
471
+ # buf, err := yaml.Marshal(configValue)
472
+ # if nil != err {
473
+ # return model.NewSDKError(model.ErrCodeAPIInvalidConfig,
474
+ # err, "fail to marshal plugin %s config", typ+":"+pluginName)
475
+ # }
476
+ # err = yaml.Unmarshal(buf, value)
477
+ # if nil != err {
478
+ # return model.NewSDKError(model.ErrCodeAPIInvalidConfig,
479
+ # err, "fail to unmarshal plugin %s config to type %v", typ+":"+pluginName,
480
+ # reflect.ValueOf(value))
481
+ # }
482
+
483
+
484
+ # 负载均衡配置
485
+ class LoadBalancerConfig:
486
+ def __init__(self, type: str = "", plugin: Dict = None):
487
+ # //负载均衡类型
488
+ # Type string `yaml:"type"`
489
+ # // 插件相关配置
490
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
491
+ self.type = type
492
+ if plugin is None:
493
+ self.plugin = {}
494
+ else:
495
+ self.plugin = plugin
496
+
497
+ def get_type(self):
498
+ return self.type
499
+
500
+ def get_plugin_config(self, plugin_name: str, value):
501
+ return load_plugin_config("loadbalancer", plugin_name, self.plugin, value)
502
+
503
+ def verify(self):
504
+ pass
505
+
506
+ def set_default(self):
507
+ if len(self.type) == 0:
508
+ self.type = DefaultConf.DefaultLoadBalancerWR
509
+ elif self.type == "wr":
510
+ self.type = DefaultConf.DefaultLoadBalancerWR
511
+
512
+
513
+ # 熔断相关配置
514
+ class CircuitBreakerConfig:
515
+ """Configure local default instance circuit breaking; durations are milliseconds."""
516
+
517
+ def __init__(self, enable: bool | None = None, check_period: int | None = None, chain: List[str] = None,
518
+ plugin: Dict = None, sleep_window: int | None = None,
519
+ success_count_after_half_open: int | None = None, default_rule_enable: bool | None = None,
520
+ default_error_count: int | None = None, default_error_percent: int | None = None,
521
+ default_interval: int | None = None, default_minimum_request: int | None = None):
522
+ # //是否启动熔断
523
+ # Enable *bool `yaml:"enable"`
524
+ # //熔断器定时检查周期
525
+ # CheckPeriod *time.Duration `yaml:"checkPeriod"`
526
+ # //熔断插件链
527
+ # Chain []string `yaml:"chain"`
528
+ # // 插件相关配置
529
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
530
+ self.enable = enable
531
+ self.check_period = check_period
532
+ self.sleep_window = sleep_window
533
+ self.success_count_after_half_open = success_count_after_half_open
534
+ self.default_rule_enable = default_rule_enable
535
+ self.default_error_count = default_error_count
536
+ self.default_error_percent = default_error_percent
537
+ self.default_interval = default_interval
538
+ self.default_minimum_request = default_minimum_request
539
+ if chain is None:
540
+ self.chain = []
541
+ else:
542
+ self.chain = chain
543
+ if plugin is None:
544
+ self.plugin = {}
545
+ else:
546
+ self.plugin = plugin
547
+
548
+ def is_enable(self):
549
+ return bool(self.enable)
550
+
551
+ def get_chain(self):
552
+ return self.chain
553
+
554
+ def get_check_period(self):
555
+ return self.check_period
556
+
557
+ def get_sleep_window(self) -> int:
558
+ """Return the open-to-half-open delay in milliseconds."""
559
+ return self.sleep_window
560
+
561
+ def get_success_count_after_half_open(self) -> int:
562
+ """Return the recovery success threshold and half-open permit count."""
563
+ return self.success_count_after_half_open
564
+
565
+ def is_default_rule_enable(self) -> bool:
566
+ """Return whether the local default instance rule is enabled."""
567
+ return bool(self.default_rule_enable)
568
+
569
+ def get_default_error_count(self) -> int:
570
+ """Return the consecutive failure threshold."""
571
+ return self.default_error_count
572
+
573
+ def get_default_error_percent(self) -> int:
574
+ """Return the error-rate threshold as an integer percentage."""
575
+ return self.default_error_percent
576
+
577
+ def get_default_interval(self) -> int:
578
+ """Return the error-rate window and delayed check interval in milliseconds."""
579
+ return self.default_interval
580
+
581
+ def get_default_minimum_request(self) -> int:
582
+ """Return the minimum sample count for an error-rate check."""
583
+ return self.default_minimum_request
584
+
585
+ def get_plugin_config(self, plugin_name: str, value):
586
+ return load_plugin_config("circuitbreaker", plugin_name, self.plugin, value)
587
+
588
+ def verify(self):
589
+ for name in ("enable", "default_rule_enable"):
590
+ if not isinstance(getattr(self, name), bool):
591
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
592
+ "CircuitBreakerConfig: %s must be boolean" % name)
593
+ for name, minimum in (
594
+ ("check_period", DefaultConf.MinCircuitBreakerCheckPeriod),
595
+ ("sleep_window", DefaultConf.MinSleepWindow),
596
+ ("success_count_after_half_open", 1),
597
+ ("default_error_count", 1),
598
+ ("default_error_percent", 0),
599
+ ("default_interval", 1),
600
+ ("default_minimum_request", 1),
601
+ ):
602
+ value = getattr(self, name)
603
+ if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
604
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
605
+ "CircuitBreakerConfig: %s must be integer >= %d" % (name, minimum))
606
+ if self.default_error_percent > 100:
607
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
608
+ "CircuitBreakerConfig: default_error_percent must be in range [0, 100]")
609
+
610
+ def set_default(self):
611
+ defaults = {
612
+ "enable": DefaultConf.DefaultCircuitBreakerEnabled,
613
+ "check_period": DefaultConf.DefaultCircuitBreakerCheckPeriod,
614
+ "sleep_window": DefaultConf.DefaultSleepWindow,
615
+ "success_count_after_half_open": DefaultConf.DefaultSuccessCountAfterHalfOpen,
616
+ "default_rule_enable": DefaultConf.DefaultRuleEnable,
617
+ "default_error_count": DefaultConf.DefaultErrorCount,
618
+ "default_error_percent": DefaultConf.DefaultErrorPercent,
619
+ "default_interval": DefaultConf.DefaultInterval,
620
+ "default_minimum_request": DefaultConf.DefaultMinimumRequest,
621
+ }
622
+ for name, value in defaults.items():
623
+ if getattr(self, name) is None:
624
+ setattr(self, name, value)
625
+ if type(self.success_count_after_half_open) is int and self.success_count_after_half_open == 0:
626
+ self.success_count_after_half_open = DefaultConf.DefaultSuccessCountAfterHalfOpen
627
+ if len(self.chain) == 0:
628
+ self.chain = [DefaultConf.DefaultCircuitBreaker]
629
+ else:
630
+ self.chain = [DefaultConf.DefaultCircuitBreaker if name == "err_count" else name for name in self.chain]
631
+
632
+
633
+ # 消费者配置
634
+ class ConsumerConfig(SelfVerifier):
635
+ def __init__(self, local_cache: LocalCacheConfig = None, service_router: ServiceRouterConfig = None,
636
+ load_balance: LoadBalancerConfig = None,
637
+ circuit_breaker: CircuitBreakerConfig = None,
638
+ weight_adjust: "WeightAdjustConfig" = None):
639
+ # LocalCache *LocalCacheConfigImpl `yaml:"localCache"`
640
+ # ServiceRouter *ServiceRouterConfigImpl `yaml:"serviceRouter"`
641
+ # Loadbalancer *LoadBalancerConfigImpl `yaml:"loadbalancer"`
642
+ # CircuitBreaker *CircuitBreakerConfigImpl `yaml:"circuitbreaker"`
643
+ self.local_cache = local_cache
644
+ self.service_router = service_router
645
+ self.load_balance = load_balance
646
+ self.circuit_breaker = circuit_breaker
647
+ self.weight_adjust = weight_adjust
648
+
649
+ def get_service_router(self) -> ServiceRouterConfig:
650
+ return self.service_router
651
+
652
+ def get_load_balancer(self) -> LoadBalancerConfig:
653
+ return self.load_balance
654
+
655
+ def get_circuit_breaker(self) -> CircuitBreakerConfig:
656
+ return self.circuit_breaker
657
+
658
+ def get_local_cache(self) -> LocalCacheConfig:
659
+ return self.local_cache
660
+
661
+ def get_weight_adjust(self) -> "WeightAdjustConfig":
662
+ return self.weight_adjust
663
+
664
+ def verify(self):
665
+ try:
666
+ self.local_cache.verify()
667
+ except SDKError as err:
668
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
669
+ "ConsumerConfig: local_cache check err: %s" % err.msg)
670
+ try:
671
+ self.service_router.verify()
672
+ except SDKError as err:
673
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
674
+ "ConsumerConfig: service_router check err: %s" % err.msg)
675
+ try:
676
+ self.load_balance.verify()
677
+ except SDKError as err:
678
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
679
+ "ConsumerConfig: load_balance check err: %s" % err.msg)
680
+ self.circuit_breaker.verify()
681
+ if self.weight_adjust is not None:
682
+ self.weight_adjust.verify()
683
+
684
+ def set_default(self):
685
+ if not self.local_cache:
686
+ self.local_cache = LocalCacheConfig()
687
+ if not self.service_router:
688
+ self.service_router = ServiceRouterConfig()
689
+ if not self.load_balance:
690
+ self.load_balance = LoadBalancerConfig()
691
+ if not self.circuit_breaker:
692
+ self.circuit_breaker = CircuitBreakerConfig()
693
+ if not self.weight_adjust:
694
+ self.weight_adjust = WeightAdjustConfig()
695
+ self.load_balance.set_default()
696
+ self.service_router.set_default()
697
+ self.local_cache.set_default()
698
+ self.service_router.set_default()
699
+ self.circuit_breaker.set_default()
700
+ self.weight_adjust.set_default()
701
+
702
+
703
+ class WeightAdjustConfig(SelfVerifier):
704
+ """Configure dynamic instance weight adjusters such as warmup."""
705
+
706
+ def __init__(self, enable: bool | None = None, chain: List[str] = None):
707
+ self.enable = enable
708
+ self.chain = chain
709
+
710
+ def is_enable(self) -> bool:
711
+ return self.enable
712
+
713
+ def get_chain(self) -> List[str]:
714
+ return self.chain
715
+
716
+ def set_default(self):
717
+ if self.enable is None:
718
+ self.enable = True
719
+ if self.chain is None:
720
+ self.chain = []
721
+
722
+ def verify(self):
723
+ if not isinstance(self.enable, bool):
724
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
725
+ "ConsumerConfig: weight_adjust.enable must be a boolean")
726
+ if (not isinstance(self.chain, list)
727
+ or any(not isinstance(name, str) or not name for name in self.chain)):
728
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
729
+ "ConsumerConfig: weight_adjust.chain must be a list of plugin names")
730
+
731
+
732
+ # 对接注册中心相关配置
733
+ class ServerConnectorConfig:
734
+ def __init__(self, addresses=None, protocol="", connect_timeout=None, message_time=None,
735
+ connection_idle_timeout: int | None = None, request_queue_size: int | None = None,
736
+ server_switch_interval: int | None = None, plugin: Dict = None, token: str = "",
737
+ trusted_ca_file: str = "", cert_file: str = "", key_file: str = ""):
738
+ # Addresses []string `yaml:"addresses"`
739
+ # Protocol string `yaml:"protocol"`
740
+ # ConnectTimeout *time.Duration `yaml:"connectTimeout"`
741
+ # //远程请求超时时间
742
+ # MessageTimeout *time.Duration `yaml:"messageTimeout"`
743
+ # ConnectionIdleTimeout *time.Duration `yaml:"connectionIdleTimeout"`
744
+ # RequestQueueSize *int32 `yaml:"requestQueueSize"`
745
+ # ServerSwitchInterval *time.Duration `yaml:"serverSwitchInterval"`
746
+ # Plugin map[string]map[string]interface{} `yaml:"plugin"`
747
+ self.addresses = addresses
748
+ self.protocol = protocol
749
+ self.connect_timeout = connect_timeout
750
+ self.message_time = message_time
751
+ self.connection_idle_timeout = connection_idle_timeout
752
+ self.request_queue_size = request_queue_size
753
+ self.server_switch_interval = server_switch_interval
754
+ self.token = token
755
+ self.trusted_ca_file = trusted_ca_file
756
+ self.cert_file = cert_file
757
+ self.key_file = key_file
758
+ if plugin is None:
759
+ self.plugin = {}
760
+ else:
761
+ self.plugin = plugin
762
+
763
+ def get_protocol(self):
764
+ return self.protocol
765
+
766
+ def get_addresses(self):
767
+ return self.addresses
768
+
769
+ def get_token(self):
770
+ return self.token
771
+
772
+ def get_connect_timeout(self):
773
+ return self.connect_timeout
774
+
775
+ def get_message_timeout(self):
776
+ return self.message_time
777
+
778
+ def get_request_queue_size(self):
779
+ return self.request_queue_size
780
+
781
+ def get_server_switch_interval(self):
782
+ return self.server_switch_interval
783
+
784
+ def get_connect_idle_timeout(self):
785
+ return self.connection_idle_timeout
786
+
787
+ def get_plugin_config(self, plugin_name: str, value):
788
+ return load_plugin_config("registry", plugin_name, self.plugin, value)
789
+
790
+ def verify(self):
791
+ for name in ("trusted_ca_file", "cert_file", "key_file"):
792
+ if not isinstance(getattr(self, name), str):
793
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, f"serverConnector.{name} must be a string")
794
+ if bool(self.cert_file) != bool(self.key_file):
795
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "TLS certFile and keyFile must be paired")
796
+ if len(self.addresses) == 0:
797
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
798
+ "ServerConnectorConfig: global.serverConnector.addresses is empty")
799
+ if self.request_queue_size < 0:
800
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
801
+ "ServerConnectorConfig: global.serverConnector.requestQueueSize %d is invalid" %
802
+ self.request_queue_size)
803
+ if self.connection_idle_timeout < DefaultConf.DefaultMinTimingInterval:
804
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
805
+ "ServerConnectorConfig: global.serverConnector.connectionIdleTimeout %d is less than "
806
+ "minimal timing interval %d" % (
807
+ self.connection_idle_timeout, DefaultConf.DefaultMinTimingInterval))
808
+ if self.server_switch_interval <= self.connection_idle_timeout:
809
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
810
+ "ServerConnectorConfig: global.serverConnector.serverSwitchInterval %d is "
811
+ "less than or equal to global. serverConnector.connectionIdleTimeout %d"
812
+ % (self.connection_idle_timeout, DefaultConf.DefaultMinTimingInterval))
813
+
814
+ def set_default(self):
815
+ if not self.addresses:
816
+ self.addresses = [ip_from_int_with_port(ipstr) for ipstr in DefaultConf.DefaultBuiltInServer]
817
+ if self.connect_timeout is None:
818
+ self.connect_timeout = DefaultConf.DefaultServerConnectTimeout
819
+ if self.message_time is None:
820
+ self.message_time = DefaultConf.DefaultServerMessageTimeout
821
+ if self.connection_idle_timeout is None:
822
+ self.connection_idle_timeout = DefaultConf.DefaultServerConnectionIdleTimeout
823
+ if self.request_queue_size is None:
824
+ self.request_queue_size = DefaultConf.DefaultRequestQueueSize
825
+ if self.server_switch_interval is None:
826
+ self.server_switch_interval = DefaultConf.DefaultServerSwitchInterval
827
+ if len(self.protocol) == 0:
828
+ self.protocol = DefaultConf.DefaultServerConnector
829
+
830
+
831
+ def ip_from_int_with_port(ipstr):
832
+ ip_parts = ipstr.split(":")
833
+ try:
834
+ host = str(ipaddress.IPv4Address(int(ip_parts[0])))
835
+ except ValueError:
836
+ host = ip_parts[0]
837
+ return "%s:%s" % (host, ip_parts[1])
838
+
839
+
840
+ class GlobalConfig(SelfVerifier):
841
+ def __init__(self, system_conf: SystemConfig = None, api_conf: APIConfig = None,
842
+ server_connector: ServerConnectorConfig = None, stat_reporter: StatReporterConfig = None,
843
+ admin: AdminConfig = None, event_reporter: EventReporterConfig = None):
844
+ # System *SystemConfigImpl `yaml:"system"`
845
+ # API *APIConfigImpl `yaml:"api"`
846
+ # ServerConnector *ServerConnectorConfigImpl `yaml:"serverConnector"`
847
+ # StatReporter *StatReporterConfigImpl `yaml:"statReporter"`
848
+ self.system_conf = system_conf
849
+ self.api_conf = api_conf
850
+ self.server_connector = server_connector
851
+ self.stat_reporter = stat_reporter
852
+ self.admin = admin
853
+ self.event_reporter = event_reporter
854
+
855
+ def get_admin(self):
856
+ """Return the context's metrics listener configuration."""
857
+ return self.admin
858
+
859
+ # 获取系统配置
860
+ def get_system(self) -> SystemConfig:
861
+ return self.system_conf
862
+
863
+ # global.api前缀开头的所有配置项
864
+ def get_api(self) -> APIConfig:
865
+ return self.api_conf
866
+
867
+ # global.registry前缀开头的所有配置项
868
+ def get_server_connector(self) -> ServerConnectorConfig:
869
+ return self.server_connector
870
+
871
+ # cl5.global.statReporter前缀开头的所有配置项
872
+ def get_stat_reporter(self) -> StatReporterConfig:
873
+ return self.stat_reporter
874
+
875
+ def get_event_reporter(self) -> EventReporterConfig:
876
+ """Return the event reporter chain configuration."""
877
+ return self.event_reporter
878
+
879
+ def verify(self):
880
+ self.admin.verify()
881
+ self.stat_reporter.verify()
882
+ self.event_reporter.verify()
883
+ try:
884
+ self.system_conf.verify()
885
+ except SDKError as err:
886
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
887
+ "GlobalConfig: system_conf check err: %s" % err.msg)
888
+ try:
889
+ self.api_conf.verify()
890
+ except SDKError as err:
891
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
892
+ "GlobalConfig: api_conf check err: %s" % err.msg)
893
+ try:
894
+ self.server_connector.verify()
895
+ except SDKError as err:
896
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
897
+ "GlobalConfig: server_connector check err: %s" % err.msg)
898
+
899
+ def set_default(self):
900
+ if self.admin is None:
901
+ self.admin = AdminConfig()
902
+ self.admin.set_default()
903
+ if not self.api_conf:
904
+ self.api_conf = APIConfig()
905
+ if not self.system_conf:
906
+ self.system_conf = SystemConfig()
907
+ if not self.server_connector:
908
+ self.server_connector = ServerConnectorConfig()
909
+ if not self.stat_reporter:
910
+ self.stat_reporter = StatReporterConfig()
911
+ if not self.event_reporter:
912
+ self.event_reporter = EventReporterConfig()
913
+ self.api_conf.set_default()
914
+ self.system_conf.set_default()
915
+ self.server_connector.set_default()
916
+ self.stat_reporter.set_default()
917
+ self.event_reporter.set_default()
918
+
919
+
920
+ class LosslessConfig(SelfVerifier):
921
+ """Configure lossless online/offline behaviour, aligned with the Java SDK."""
922
+
923
+ DELAY_BY_TIME = "DELAY_BY_TIME"
924
+ DELAY_BY_HEALTH_CHECK = "DELAY_BY_HEALTH_CHECK"
925
+ STRATEGIES = (DELAY_BY_TIME, DELAY_BY_HEALTH_CHECK)
926
+
927
+ def __init__(self, enable: bool | None = None, strategy: str | None = None,
928
+ delay_register_interval: int | None = None, health_check_interval: int | None = None,
929
+ health_check_protocol: str = "http", health_check_path: str = "",
930
+ health_check_method: str = "GET"):
931
+ self.enable = enable
932
+ self.strategy = strategy
933
+ self.delay_register_interval = delay_register_interval
934
+ self.health_check_interval = health_check_interval
935
+ self.health_check_protocol = health_check_protocol
936
+ self.health_check_path = health_check_path
937
+ self.health_check_method = health_check_method
938
+
939
+ def is_enable(self) -> bool:
940
+ return self.enable
941
+
942
+ def get_strategy(self) -> str:
943
+ return self.strategy
944
+
945
+ def get_delay_register_interval(self) -> int:
946
+ return self.delay_register_interval
947
+
948
+ def get_health_check_interval(self) -> int:
949
+ return self.health_check_interval
950
+
951
+ def get_health_check_protocol(self) -> str:
952
+ return self.health_check_protocol
953
+
954
+ def get_health_check_path(self) -> str:
955
+ return self.health_check_path
956
+
957
+ def get_health_check_method(self) -> str:
958
+ return self.health_check_method
959
+
960
+ def set_default(self):
961
+ if self.enable is None:
962
+ self.enable = False
963
+ if self.strategy is None:
964
+ self.strategy = self.DELAY_BY_TIME
965
+ if self.delay_register_interval is None:
966
+ self.delay_register_interval = 30000
967
+ if self.health_check_interval is None:
968
+ self.health_check_interval = 5000
969
+
970
+ def verify(self):
971
+ if not isinstance(self.enable, bool):
972
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
973
+ "ProviderConfig: lossless.enable must be a boolean")
974
+ if self.strategy not in self.STRATEGIES:
975
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
976
+ "ProviderConfig: lossless.strategy must be one of %s" % ", ".join(self.STRATEGIES))
977
+ for name in ("delay_register_interval", "health_check_interval"):
978
+ value = getattr(self, name)
979
+ if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
980
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
981
+ f"ProviderConfig: lossless.{name} must be positive integer milliseconds")
982
+ for name in ("health_check_protocol", "health_check_path", "health_check_method"):
983
+ if not isinstance(getattr(self, name), str):
984
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
985
+ f"ProviderConfig: lossless.{name} must be a string")
986
+ if self.health_check_protocol != "http":
987
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
988
+ "ProviderConfig: lossless.healthCheckProtocol only supports http")
989
+
990
+
991
+ class ProviderConfig(SelfVerifier):
992
+ """Configure provider registration, re-registration and lossless lifecycle."""
993
+
994
+ def __init__(self, min_register_interval: int | None = None, lossless: LosslessConfig = None):
995
+ self.min_register_interval = min_register_interval
996
+ self.lossless = lossless
997
+
998
+ def get_min_register_interval(self) -> int:
999
+ """Return the minimum re-registration interval in milliseconds."""
1000
+ return self.min_register_interval
1001
+
1002
+ def get_lossless(self) -> LosslessConfig:
1003
+ """Return the lossless online/offline configuration."""
1004
+ return self.lossless
1005
+
1006
+ def set_default(self):
1007
+ if (self.min_register_interval is None
1008
+ or type(self.min_register_interval) is int and self.min_register_interval == 0):
1009
+ self.min_register_interval = 30000
1010
+ if not self.lossless:
1011
+ self.lossless = LosslessConfig()
1012
+ self.lossless.set_default()
1013
+
1014
+ def verify(self):
1015
+ if (isinstance(self.min_register_interval, bool)
1016
+ or not isinstance(self.min_register_interval, int) or self.min_register_interval <= 0):
1017
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
1018
+ "ProviderConfig: minRegisterInterval must be positive integer milliseconds")
1019
+ if self.lossless is not None:
1020
+ self.lossless.verify()
1021
+
1022
+
1023
+ class SkillConnectorConfig(SelfVerifier):
1024
+ """Configure the independent Skill connection and persistent fallback."""
1025
+
1026
+ def __init__(self, addresses=None, protocol="grpc", connector_type="polaris", token="",
1027
+ message_timeout=5000, download_timeout=30000, persist_enable=True,
1028
+ persist_dir="./polaris/backup/skill", persist_max_write_retry=1,
1029
+ persist_max_read_retry=0, persist_retry_interval=500, fallback_to_local_cache=True,
1030
+ trusted_ca_file="", cert_file="", key_file=""):
1031
+ self.addresses = addresses
1032
+ self.protocol = protocol
1033
+ self.connector_type = connector_type
1034
+ self.token = token
1035
+ self.message_timeout = message_timeout
1036
+ self.download_timeout = download_timeout
1037
+ self.persist_enable = persist_enable
1038
+ self.persist_dir = persist_dir
1039
+ self.persist_max_write_retry = persist_max_write_retry
1040
+ self.persist_max_read_retry = persist_max_read_retry
1041
+ self.persist_retry_interval = persist_retry_interval
1042
+ self.fallback_to_local_cache = fallback_to_local_cache
1043
+ self.trusted_ca_file = trusted_ca_file
1044
+ self.cert_file = cert_file
1045
+ self.key_file = key_file
1046
+
1047
+ def set_default(self):
1048
+ """Use the standard local Skill endpoint when no address is assigned."""
1049
+ if self.addresses is None:
1050
+ self.addresses = ["127.0.0.1:8094"]
1051
+
1052
+ def verify(self):
1053
+ """Validate the supported connector and persistence settings."""
1054
+ if (self.protocol != "grpc" or self.connector_type != "polaris"
1055
+ or not isinstance(self.addresses, (list, tuple)) or not self.addresses
1056
+ or any(not isinstance(address, str) or not address.strip() for address in self.addresses)):
1057
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "ai.skill requires polaris/grpc and nonempty addresses")
1058
+ for name in ("message_timeout", "download_timeout"):
1059
+ value = getattr(self, name)
1060
+ if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
1061
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, f"ai.skill.{name} must be positive milliseconds")
1062
+ for name in ("persist_max_write_retry", "persist_max_read_retry", "persist_retry_interval"):
1063
+ value = getattr(self, name)
1064
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
1065
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, f"ai.skill.{name} must be >=0")
1066
+ for name in ("persist_enable", "fallback_to_local_cache"):
1067
+ if not isinstance(getattr(self, name), bool):
1068
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, f"ai.skill.{name} must be boolean")
1069
+ if not isinstance(self.token, str):
1070
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "ai.skill.token must be a string")
1071
+ for name in ("trusted_ca_file", "cert_file", "key_file"):
1072
+ if not isinstance(getattr(self, name), str):
1073
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, f"ai.skill.{name} must be a string")
1074
+ if bool(self.cert_file) != bool(self.key_file):
1075
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "Skill TLS certFile and keyFile must be paired")
1076
+ if self.persist_enable and (not isinstance(self.persist_dir, str) or not self.persist_dir):
1077
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "ai.skill.persistDir must not be empty")
1078
+
1079
+
1080
+ class SkillConfig(SelfVerifier):
1081
+ """Hold the Skill connector settings."""
1082
+
1083
+ def __init__(self, server_connector=None):
1084
+ self.server_connector = server_connector
1085
+
1086
+ def get_server_connector(self):
1087
+ """Return the independent Skill connector configuration."""
1088
+ return self.server_connector
1089
+
1090
+ def set_default(self):
1091
+ """Fill missing connector settings."""
1092
+ if self.server_connector is None:
1093
+ self.server_connector = SkillConnectorConfig()
1094
+ self.server_connector.set_default()
1095
+
1096
+ def verify(self):
1097
+ """Validate the Skill connector."""
1098
+ self.server_connector.verify()
1099
+
1100
+
1101
+ class AIConfig(SelfVerifier):
1102
+ """Hold AI registry client settings."""
1103
+
1104
+ def __init__(self, skill=None):
1105
+ self.skill = skill
1106
+
1107
+ def get_skill(self):
1108
+ """Return the Skill client configuration."""
1109
+ return self.skill
1110
+
1111
+ def set_default(self):
1112
+ """Fill missing AI configuration."""
1113
+ if self.skill is None:
1114
+ self.skill = SkillConfig()
1115
+ self.skill.set_default()
1116
+
1117
+ def verify(self):
1118
+ """Validate configured AI capabilities."""
1119
+ self.skill.verify()
1120
+
1121
+
1122
+ class Configuration(SelfVerifier):
1123
+ def __init__(self, global_config: GlobalConfig = None, consumer_config: ConsumerConfig = None,
1124
+ provider_config: ProviderConfig = None, ai: AIConfig = None):
1125
+ # Global *GlobalConfigImpl `yaml:"global"`
1126
+ # Consumer *ConsumerConfigImpl `yaml:"consumer"`
1127
+ self.global_config = global_config
1128
+ self.consumer_config = consumer_config
1129
+ self.provider_config = provider_config
1130
+ self.ai = ai
1131
+
1132
+ def get_ai(self):
1133
+ """Return AI registry settings for this configuration."""
1134
+ return self.ai
1135
+
1136
+ def get_global(self) -> GlobalConfig:
1137
+ return self.global_config
1138
+
1139
+ def get_consumer(self) -> ConsumerConfig:
1140
+ return self.consumer_config
1141
+
1142
+ def get_provider(self) -> ProviderConfig:
1143
+ return self.provider_config
1144
+
1145
+ def verify(self):
1146
+ self.global_config.verify()
1147
+ self.consumer_config.verify()
1148
+ self.provider_config.verify()
1149
+ self.ai.verify()
1150
+
1151
+ def set_default(self):
1152
+ if not self.global_config:
1153
+ self.global_config = GlobalConfig()
1154
+ if not self.consumer_config:
1155
+ self.consumer_config = ConsumerConfig()
1156
+ if not self.provider_config:
1157
+ self.provider_config = ProviderConfig()
1158
+ self.global_config.set_default()
1159
+ self.consumer_config.set_default()
1160
+ self.provider_config.set_default()
1161
+ if self.ai is None:
1162
+ self.ai = AIConfig()
1163
+ self.ai.set_default()
1164
+ skill_connector = self.ai.get_skill().get_server_connector()
1165
+ global_connector = self.global_config.get_server_connector()
1166
+ if not skill_connector.token and global_connector.token:
1167
+ skill_connector.token = global_connector.token
1168
+
1169
+ @classmethod
1170
+ def from_dict(cls, data: Dict, apply_env: bool = True) -> "Configuration":
1171
+ """Create a configuration from a mapping, with environment overrides by default."""
1172
+ from polaris.pkg.config.loader import build_configuration
1173
+ return build_configuration(data, apply_env=apply_env)
1174
+
1175
+ @classmethod
1176
+ def from_file(cls, path: str, apply_env: bool = True) -> "Configuration":
1177
+ """Create a configuration from a YAML file."""
1178
+ from polaris.pkg.config.loader import build_configuration, load_config_file
1179
+ return build_configuration(load_config_file(path), apply_env=apply_env)
1180
+
1181
+ @classmethod
1182
+ def from_env(cls) -> "Configuration":
1183
+ """Create a configuration from POLARIS_ environment variables."""
1184
+ from polaris.pkg.config.loader import build_configuration
1185
+ return build_configuration({}, apply_env=True)
1186
+
1187
+
1188
+ # 服务集群信息转换为服务对象
1189
+ def service_cluster_to_service_key(config: ServerClusterConfig) -> ServiceKey:
1190
+ return ServiceKey(namespace=config.get_namespace(), service=config.get_service())
1191
+
1192
+
1193
+ def get_server_services(config: Configuration) -> Dict[str, ClusterService]:
1194
+ """Return configured system services, omitting unnamed clusters as the Go SDK does."""
1195
+ system = config.get_global().get_system()
1196
+ clusters = {
1197
+ ClusterType.DiscoverCluster: system.get_discover_cluster(),
1198
+ ClusterType.HealthCheckCluster: system.get_health_check_cluster(),
1199
+ ClusterType.MonitorCluster: system.get_monitor_cluster(),
1200
+ }
1201
+ return {
1202
+ cluster_type: ClusterService(service_cluster_to_service_key(cluster), cluster_type)
1203
+ for cluster_type, cluster in clusters.items()
1204
+ if cluster.get_namespace() and cluster.get_service()
1205
+ }