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,384 @@
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
+ """Server connector plugin backed by the Polaris gRPC API."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ from dataclasses import replace
22
+ from collections.abc import AsyncIterator
23
+ from contextlib import asynccontextmanager
24
+ import time
25
+
26
+ from polarismesh_specification.api.v1.service_manage import response_pb2
27
+
28
+ from polaris.pkg.model.error import ErrorCode, SDKError, ServerCode, ensure_response
29
+ from polaris.pkg.model.server import (
30
+ InstanceDeRegisterRequest,
31
+ InstanceDiscoverRequest,
32
+ InstanceHeartbeatRequest,
33
+ InstanceRegisterRequest,
34
+ InstanceRegisterResponse,
35
+ ReportClientRequest,
36
+ ReportClientResponse,
37
+ )
38
+ from polaris.pkg.model.service import GetOneInstanceRequest, InstancesResponse
39
+ from ..base import BaseServerConnector
40
+ from ..convert import (
41
+ contract_request_to_proto,
42
+ contract_response_from_proto,
43
+ deregister_request_to_proto,
44
+ discover_request_to_proto,
45
+ heartbeat_request_to_proto,
46
+ register_request_to_proto,
47
+ report_client_to_proto,
48
+ )
49
+ from polaris.plugin_manager import InitContext, PluginType, register_plugin
50
+
51
+ from .connection import GrpcConnection, GrpcSettings
52
+ from .tls import channel_credentials
53
+
54
+
55
+ @register_plugin(PluginType.SERVER_CONNECTOR, "grpc")
56
+ class GrpcConnector(BaseServerConnector):
57
+ """Adapt internal SDK request objects to specification gRPC calls."""
58
+
59
+ def __init__(self, init_ctx: InitContext) -> None:
60
+ self.init_ctx = init_ctx
61
+ self.config = init_ctx.config
62
+ connector_config = self.config.get_global().get_server_connector()
63
+ api_config = self.config.get_global().get_api()
64
+ cache_config = self.config.get_consumer().get_local_cache()
65
+ self.connection = GrpcConnection(
66
+ GrpcSettings(
67
+ addresses=connector_config.get_addresses(),
68
+ timeout_seconds=max(connector_config.get_message_timeout(), 1) / 1000,
69
+ token=connector_config.get_token(),
70
+ retry_attempts=api_config.get_max_retry_times(),
71
+ retry_backoff_seconds=max(api_config.get_retry_interval(), 0) / 1000,
72
+ refresh_interval_seconds=max(cache_config.get_service_refresh_interval(), 1) / 1000,
73
+ switch_interval_seconds=max(connector_config.get_server_switch_interval(), 1) / 1000,
74
+ idle_timeout_seconds=max(connector_config.get_connect_idle_timeout(), 1) / 1000,
75
+ credentials=channel_credentials(connector_config),
76
+ ),
77
+ context_logger=init_ctx.logger,
78
+ )
79
+ self._streams: dict[str, AsyncIterator[response_pb2.DiscoverResponse]] = {}
80
+ self._stream_locks: dict[str, asyncio.Lock] = {}
81
+ self._closed = False
82
+ self.engine = None
83
+ self._cluster_connections = {}
84
+ self._stream_connections = {}
85
+ self._stream_last_activity = {}
86
+ self._idle_task = None
87
+ self._request_slots = asyncio.Semaphore(connector_config.get_request_queue_size() + 1)
88
+ self.logger = init_ctx.logger.get_logger("network") if init_ctx.logger is not None else None
89
+ self.base_logger = init_ctx.logger.get_logger("base") if init_ctx.logger is not None else None
90
+
91
+ @asynccontextmanager
92
+ async def _request_slot(self):
93
+ """Bound concurrent requests; extra callers wait like Go's buffered task channel."""
94
+ await self._request_slots.acquire()
95
+ try:
96
+ yield
97
+ finally:
98
+ self._request_slots.release()
99
+
100
+ def start_address_switching(self):
101
+ """Start periodic address rotation and idle stream cleanup."""
102
+ self._start_address_switching(self.connection, self.engine.loop if self.engine is not None else None)
103
+ self._start_idle_cleanup(self.engine.loop if self.engine is not None else None)
104
+
105
+ def _start_address_switching(self, connection, loop=None):
106
+ connection.on_switch = lambda: self._on_address_switched(connection)
107
+ connection.start_switching(loop)
108
+
109
+ def _start_idle_cleanup(self, loop=None):
110
+ if self._closed or self._idle_task is not None:
111
+ return
112
+ loop = loop or asyncio.get_running_loop()
113
+ self._idle_task = loop.create_task(self._idle_cleanup_loop(), name="polaris-idle-stream-cleanup")
114
+
115
+ async def _idle_cleanup_loop(self):
116
+ interval = self.connection.settings.idle_timeout_seconds
117
+ while not self._closed:
118
+ await asyncio.sleep(interval)
119
+ if self._closed:
120
+ return
121
+ await self.close_idle_streams()
122
+
123
+ async def close_idle_streams(self):
124
+ """Close idle Discover streams while retaining shared gRPC channels."""
125
+ timeout = self.connection.settings.idle_timeout_seconds
126
+ now = time.monotonic()
127
+ for key, last_activity in list(self._stream_last_activity.items()):
128
+ if now - last_activity < timeout:
129
+ continue
130
+ lock = self._stream_locks.get(key)
131
+ if lock is None or lock.locked():
132
+ continue
133
+ async with lock:
134
+ current_activity = self._stream_last_activity.get(key)
135
+ if current_activity != last_activity or now - current_activity < timeout:
136
+ continue
137
+ stream = self._streams.pop(key, None)
138
+ self._stream_connections.pop(key, None)
139
+ self._stream_last_activity.pop(key, None)
140
+ self._stream_locks.pop(key, None)
141
+ if stream is not None:
142
+ await stream.aclose()
143
+ if self.logger is not None:
144
+ self.logger.debug("closed idle Discover stream %s", key)
145
+
146
+ async def _stop_idle_cleanup(self):
147
+ if self._idle_task is not None:
148
+ self._idle_task.cancel()
149
+ await asyncio.gather(self._idle_task, return_exceptions=True)
150
+ self._idle_task = None
151
+
152
+ async def _on_address_switched(self, connection):
153
+ """Close streams pinned to the old endpoint so the next read reconnects."""
154
+ keys = [key for key, owner in self._stream_connections.items() if owner is connection]
155
+ for key in keys:
156
+ stream = self._streams.get(key)
157
+ if stream is None:
158
+ self._stream_connections.pop(key, None)
159
+ continue
160
+ lock = self._stream_locks.get(key)
161
+ if lock is not None and not lock.locked():
162
+ async with lock:
163
+ if self._streams.get(key) is stream:
164
+ self._streams.pop(key, None)
165
+ self._stream_connections.pop(key, None)
166
+ self._stream_last_activity.pop(key, None)
167
+ await stream.aclose()
168
+ else:
169
+ await stream.aclose()
170
+ self._streams.pop(key, None)
171
+ self._stream_connections.pop(key, None)
172
+ self._stream_last_activity.pop(key, None)
173
+
174
+ async def _connection(self, heartbeat=False):
175
+ system = self.config.get_global().get_system()
176
+ cluster = system.get_health_check_cluster() if heartbeat else system.get_discover_cluster()
177
+ if not cluster.namespace or not cluster.service or self.engine is None:
178
+ return self.connection
179
+ key = (cluster.namespace, cluster.service)
180
+ from polaris.pkg.model.service import GetInstancesRequest
181
+ snapshot = await self.engine._query_instances(GetInstancesRequest(
182
+ namespace=cluster.namespace, service=cluster.service), "healthy")
183
+ if snapshot is None or not snapshot.instances:
184
+ raise SDKError(ErrorCode.ErrCodeAPIInstanceNotFound, f"No system service instance for {key}")
185
+ addresses = []
186
+ for node in snapshot.instances:
187
+ host = node.get_host()
188
+ addresses.append(f"[{host}]:{node.get_port()}" if ":" in host else f"{host}:{node.get_port()}")
189
+ if key not in self._cluster_connections:
190
+ connection = GrpcConnection(replace(self.connection.settings, addresses=addresses),
191
+ context_logger=self.init_ctx.logger)
192
+ connection.target_service = key
193
+ connection.stat_reporter = self.connection.stat_reporter
194
+ connection.address_resolver = lambda: self._connection(heartbeat=heartbeat)
195
+ self._cluster_connections[key] = connection
196
+ connection = self._cluster_connections[key]
197
+ self._start_address_switching(connection)
198
+ connection._addresses = addresses
199
+ return connection
200
+
201
+ async def register_instance(self, request: InstanceRegisterRequest) -> InstanceRegisterResponse:
202
+ connection = await self._connection()
203
+ async with self._request_slot():
204
+ response = await connection.register_instance(
205
+ register_request_to_proto(request), auto_heartbeat=request.auto_heartbeat,
206
+ )
207
+ ensure_response(response, (ServerCode.ExecuteSuccess, ServerCode.ExistedResource))
208
+ instance_id = response.instance.id.value if response.HasField("instance") else ""
209
+ if self.logger is not None:
210
+ self.logger.info(
211
+ "instance registered namespace=%s service=%s host=%s port=%s instanceId=%s autoHeartbeat=%s",
212
+ request.namespace, request.service, request.host, request.port, instance_id,
213
+ request.auto_heartbeat,
214
+ )
215
+ if self.base_logger is not None:
216
+ metadata = dict(getattr(request, "metadata", {}) or {})
217
+ self.base_logger.info(
218
+ "registerInstance success namespace=%s service=%s host=%s port=%s "
219
+ "instanceId=%s metadataSize=%d metadataKeys=%s",
220
+ request.namespace, request.service, request.host, request.port, instance_id,
221
+ len(metadata), sorted(metadata),
222
+ )
223
+ return InstanceRegisterResponse(instance_id)
224
+
225
+ async def deregister_instance(self, request: InstanceDeRegisterRequest) -> None:
226
+ connection = await self._connection()
227
+ async with self._request_slot():
228
+ response = await connection.deregister_instance(deregister_request_to_proto(request))
229
+ ensure_response(response, (ServerCode.ExecuteSuccess, ServerCode.NotFoundResource))
230
+ if self.logger is not None:
231
+ self.logger.info(
232
+ "instance deregistered namespace=%s service=%s host=%s port=%s instanceId=%s",
233
+ request.namespace, request.service, request.host, request.port, request.instance_id,
234
+ )
235
+ if self.base_logger is not None:
236
+ self.base_logger.info(
237
+ "deregisterInstance success namespace=%s service=%s host=%s port=%s instanceId=%s",
238
+ request.namespace, request.service, request.host, request.port, request.instance_id,
239
+ )
240
+
241
+ @staticmethod
242
+ def _stream_key(request: InstanceDiscoverRequest) -> str:
243
+ return f"{request.namespace}\\x00{request.name}\\x00{request.type}"
244
+
245
+ async def discover(
246
+ self,
247
+ request: InstanceDiscoverRequest,
248
+ use_discover_cache: bool = True,
249
+ ) -> response_pb2.DiscoverResponse:
250
+ del use_discover_cache
251
+ if self._closed:
252
+ raise SDKError(ErrorCode.ErrCodeInvalidStateError, "gRPC connector has been closed")
253
+ key = self._stream_key(request)
254
+ lock = self._stream_locks.setdefault(key, asyncio.Lock())
255
+ async with lock:
256
+ stream = self._streams.get(key)
257
+ if stream is None:
258
+ system = self.config.get_global().get_system()
259
+ system_keys = {(system.get_discover_cluster().namespace, system.get_discover_cluster().service),
260
+ (system.get_health_check_cluster().namespace, system.get_health_check_cluster().service)}
261
+ connection = (self.connection if (request.namespace, request.name) in system_keys
262
+ else await self._connection())
263
+ stream = connection.discover(discover_request_to_proto(request))
264
+ self._streams[key] = stream
265
+ self._stream_connections[key] = connection
266
+ self._stream_last_activity[key] = time.monotonic()
267
+ if self.logger is not None:
268
+ self.logger.info(
269
+ "Discover stream opened namespace=%s service=%s type=%s revision=%s",
270
+ request.namespace, request.name, request.type, request.revision,
271
+ )
272
+ try:
273
+ async with self._request_slot():
274
+ response = await anext(stream)
275
+ self._stream_last_activity[key] = time.monotonic()
276
+ except StopAsyncIteration:
277
+ self._streams.pop(key, None)
278
+ self._stream_connections.pop(key, None)
279
+ self._stream_last_activity.pop(key, None)
280
+ if self.logger is not None:
281
+ self.logger.warning(
282
+ "Discover stream closed namespace=%s service=%s type=%s",
283
+ request.namespace, request.name, request.type,
284
+ )
285
+ raise SDKError(ErrorCode.ErrCodeNetworkError, f"Discover stream closed for {request.name}")
286
+ except (asyncio.CancelledError, Exception):
287
+ self._streams.pop(key, None)
288
+ self._stream_connections.pop(key, None)
289
+ self._stream_last_activity.pop(key, None)
290
+ await stream.aclose()
291
+ raise
292
+ ensure_response(response)
293
+ if self.logger is not None:
294
+ self.logger.debug(
295
+ "Discover response namespace=%s service=%s type=%s revision=%s instances=%s",
296
+ request.namespace, request.name, request.type, response.service.revision.value,
297
+ len(response.instances),
298
+ )
299
+ return response
300
+
301
+ async def stop_discover(self, request: InstanceDiscoverRequest) -> None:
302
+ """Close one idle resource stream without closing shared gRPC channels."""
303
+ key = self._stream_key(request)
304
+ lock = self._stream_locks.get(key)
305
+ if lock is None:
306
+ return
307
+ async with lock:
308
+ stream = self._streams.pop(key, None)
309
+ self._stream_connections.pop(key, None)
310
+ self._stream_last_activity.pop(key, None)
311
+ try:
312
+ if stream is not None:
313
+ await stream.aclose()
314
+ finally:
315
+ self._stream_locks.pop(key, None)
316
+ if self.logger is not None:
317
+ self.logger.info(
318
+ "Discover stream stopped namespace=%s service=%s type=%s",
319
+ request.namespace, request.name, request.type,
320
+ )
321
+
322
+ async def heartbeat(self, request: InstanceHeartbeatRequest) -> None:
323
+ connection = await self._connection(heartbeat=True)
324
+ async with self._request_slot():
325
+ response = await connection.heartbeat(heartbeat_request_to_proto(request))
326
+ ensure_response(response)
327
+
328
+ async def report_client(self, request: ReportClientRequest) -> ReportClientResponse:
329
+ connection = await self._connection()
330
+ async with self._request_slot():
331
+ response = await connection.report_client(report_client_to_proto(request))
332
+ ensure_response(response)
333
+ if not response.HasField("client") or not response.client.HasField("location"):
334
+ return ReportClientResponse("", "", "")
335
+ location = response.client.location
336
+ return ReportClientResponse(location.region.value, location.zone.value, location.campus.value)
337
+
338
+ async def report_service_contract(self, request):
339
+ """Report the contract over the discovery connection's channel."""
340
+ connection = await self._connection()
341
+ async with self._request_slot():
342
+ response = await connection._unary(
343
+ "ReportServiceContract", contract_request_to_proto(request),
344
+ timeout_seconds=request.timeout / 1000 if request.timeout else None,
345
+ )
346
+ ensure_response(response)
347
+
348
+ async def get_service_contract(self, request):
349
+ """Retrieve a contract without adding another discovery cache type."""
350
+ connection = await self._connection()
351
+ async with self._request_slot():
352
+ response = await connection._unary(
353
+ "GetServiceContract", contract_request_to_proto(request),
354
+ timeout_seconds=request.timeout / 1000 if request.timeout else None,
355
+ )
356
+ ensure_response(response)
357
+ if not response.HasField("serviceContract"):
358
+ raise SDKError(ErrorCode.ErrCodeInvalidResponse, "missing service contract response")
359
+ return contract_response_from_proto(response.serviceContract)
360
+
361
+ def get_one_instance(self, request: GetOneInstanceRequest) -> InstancesResponse:
362
+ del request
363
+ raise SDKError(ErrorCode.ErrCodePluginError, "single-instance server RPC is not implemented")
364
+
365
+ async def close(self) -> None:
366
+ """Close discovery streams and their gRPC channels."""
367
+ if self._closed:
368
+ return
369
+ self._closed = True
370
+ await self._stop_idle_cleanup()
371
+ streams = list(self._streams.values())
372
+ self._streams.clear()
373
+ self._stream_locks.clear()
374
+ self._stream_connections.clear()
375
+ self._stream_last_activity.clear()
376
+ for stream in streams:
377
+ try:
378
+ await stream.aclose()
379
+ except RuntimeError:
380
+ pass
381
+ await self.connection.close()
382
+ for connection in self._cluster_connections.values():
383
+ await connection.close()
384
+ self._cluster_connections.clear()
@@ -0,0 +1,45 @@
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
+ """Load configured TLS material once, refusing incomplete or invalid credentials."""
17
+
18
+ from pathlib import Path
19
+ import ssl
20
+
21
+ import grpc
22
+
23
+ from polaris.pkg.model.error import ErrorCode, SDKError
24
+
25
+
26
+ def channel_credentials(config):
27
+ """Build validated gRPC credentials, or None only when no TLS files are configured."""
28
+ ca, cert, key = config.trusted_ca_file, config.cert_file, config.key_file
29
+ if not any((ca, cert, key)):
30
+ return None
31
+ if bool(cert) != bool(key):
32
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig, "TLS certFile and keyFile must be configured together")
33
+ try:
34
+ roots = Path(ca).read_bytes() if ca else None
35
+ chain = Path(cert).read_bytes() if cert else None
36
+ private_key = Path(key).read_bytes() if key else None
37
+ validator = ssl.create_default_context()
38
+ if roots is not None:
39
+ validator.load_verify_locations(cadata=roots.decode("ascii"))
40
+ if cert:
41
+ validator.load_cert_chain(cert, key, password=lambda: "")
42
+ return grpc.ssl_channel_credentials(roots, private_key, chain)
43
+ except (OSError, ValueError, UnicodeError, grpc.RpcError) as error:
44
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
45
+ f"Invalid TLS certificate configuration: {error}") from error
@@ -0,0 +1,28 @@
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
+ from .base import BaseServiceRouter, RouteInfo, RouteResult
17
+ from .isolated import IsolatedRouter
18
+ from .metadata import MetadataRouter
19
+ from .recover import RecoverRouter
20
+
21
+ __all__ = [
22
+ "BaseServiceRouter",
23
+ "IsolatedRouter",
24
+ "MetadataRouter",
25
+ "RecoverRouter",
26
+ "RouteInfo",
27
+ "RouteResult",
28
+ ]
@@ -0,0 +1,105 @@
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
+ from abc import ABC, abstractmethod
17
+ from typing import Dict, List
18
+
19
+ from polaris.pkg.model import service
20
+ from polaris.pkg.model.rule import ServiceRule
21
+ from polaris.pkg.model.service import ServiceInfo
22
+
23
+ __all__ = [
24
+ "BaseServiceRouter",
25
+ "CanaryMetaKey",
26
+ "CanaryMetadataEnable",
27
+ "MetadataFailoverKey",
28
+ "NearbyKeyAreaWithCampus",
29
+ "NearbyKeyAreaWithRegion",
30
+ "NearbyKeyAreaWithZone",
31
+ "NearbyMetadataEnable",
32
+ "RouteInfo",
33
+ "RouteResult",
34
+ "SetEnableKey",
35
+ "SetEnableValue",
36
+ "SetNameKey",
37
+ ]
38
+
39
+ # 保存客户端地域信息 idc key
40
+ NearbyKeyAreaWithCampus = "client_area_campus"
41
+ # 保存客户端地域信息 zone key
42
+ NearbyKeyAreaWithZone = "client_area_zone"
43
+ # 保存客户端地域信息 region key
44
+ NearbyKeyAreaWithRegion = "client_area_region"
45
+
46
+ NearbyMetadataEnable = "internal-enable-nearby"
47
+ CanaryMetadataEnable = "internal-canary"
48
+ CanaryMetaKey = "canary"
49
+
50
+ SetEnableKey = "internal-enable-set"
51
+ SetEnableValue = "Y"
52
+ SetNameKey = "internal-set-name"
53
+ MetadataFailoverKey = "internal-metadata-failover-type"
54
+
55
+
56
+ class RouteInfo:
57
+ def __init__(self, source_service: ServiceInfo = None, source_routerule: ServiceRule = None,
58
+ dest_service: ServiceInfo = None, dest_routerule: ServiceRule = None,
59
+ include_unhealthy_instances: bool = False, include_circuitbreak_instances: bool = False,
60
+ canary: str = "", metadata: Dict[str, str] = None,
61
+ metadata_failover_type: str = None):
62
+ # 源服务信息
63
+ self.source_service = source_service
64
+ # 源路由规则
65
+ self.source_routerule = source_routerule
66
+ # 目标服务信息
67
+ self.dest_service = dest_service
68
+ # 目标路由规则
69
+ self.dest_routerule = dest_routerule
70
+ # 是否包含不健康的服务实例,默认false
71
+ self.include_unhealthy_instances = include_unhealthy_instances
72
+ # 是否包含被熔断的服务实例,默认false
73
+ self.include_circuitbreak_instances = include_circuitbreak_instances
74
+ # 标识是否不需要执行全死全活兜底
75
+ # 对于全死全活插件,以及就近路由插件等,已经做了最小实例数检查,则可以设置该属性为true
76
+ # 需要插件内部进行设置
77
+ self.ignore_filter_only_on_end_chain = False
78
+ # 可动态调整路由插件是否启用,不存在或者为true代表启用
79
+ # key为路由插件的id
80
+ self.chain_enables = {}
81
+ # 金丝雀
82
+ self.canary = canary
83
+ # 请求 metadata,用于 metadataRouter
84
+ self.metadata = {} if metadata is None else metadata
85
+ # none / all / others;None 表示使用服务或插件配置
86
+ self.metadata_failover_type = metadata_failover_type
87
+
88
+
89
+ class RouteResult:
90
+ def __init__(self, instances: List[service.Instance]):
91
+ # 根据路由规则过滤后的目标服务实例列表,假如重定向则列表为空
92
+ self.instances = instances
93
+
94
+
95
+ class BaseServiceRouter(ABC):
96
+ """
97
+ 获取规则过滤后的服务实例列表
98
+ """
99
+
100
+ @abstractmethod
101
+ def get_filtered_instances(self, route_info: RouteInfo, instances: service.ServiceInstances) -> RouteResult:
102
+ pass
103
+
104
+ def enable(self, route_info: RouteInfo, instances: service.ServiceInstances) -> bool:
105
+ return True
@@ -0,0 +1,35 @@
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
+ from polaris.pkg.config.default import DefaultConf
17
+ from polaris.pkg.model import service
18
+ from polaris.plugin.service_router.base import BaseServiceRouter, RouteInfo, RouteResult
19
+ from polaris.plugin_manager import InitContext, PluginType, register_plugin
20
+
21
+
22
+ @register_plugin(PluginType.SERVICE_ROUTER, DefaultConf.DefaultServiceRouterIsolated)
23
+ class IsolatedRouter(BaseServiceRouter):
24
+ """Drop isolated instances and instances whose weight is zero."""
25
+
26
+ def __init__(self, init_ctx: InitContext = None):
27
+ self.init_ctx = init_ctx
28
+
29
+ def enable(self, route_info: RouteInfo, instances: service.ServiceInstances) -> bool:
30
+ return route_info.chain_enables.get(DefaultConf.DefaultServiceRouterIsolated, True)
31
+
32
+ def get_filtered_instances(self, route_info: RouteInfo, instances: service.ServiceInstances) -> RouteResult:
33
+ selected = [instance for instance in instances.get_instances()
34
+ if instance.get_weight() != 0 and not instance.is_isolated()]
35
+ return RouteResult(selected)