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.
- polaris/__init__.py +74 -0
- polaris/_version.py +16 -0
- polaris/api/__init__.py +55 -0
- polaris/api/_log_file.py +232 -0
- polaris/api/config.py +145 -0
- polaris/api/consumer.py +240 -0
- polaris/api/log.py +414 -0
- polaris/api/lossless.py +73 -0
- polaris/api/provider.py +152 -0
- polaris/api/skill.py +162 -0
- polaris/pkg/__init__.py +15 -0
- polaris/pkg/common/__init__.py +15 -0
- polaris/pkg/common/plugin.py +33 -0
- polaris/pkg/config/__init__.py +15 -0
- polaris/pkg/config/api.py +1205 -0
- polaris/pkg/config/default.py +92 -0
- polaris/pkg/config/event.py +76 -0
- polaris/pkg/config/loader.py +392 -0
- polaris/pkg/config/stat_reporter.py +100 -0
- polaris/pkg/flow/__init__.py +15 -0
- polaris/pkg/flow/admin_server.py +104 -0
- polaris/pkg/flow/async_flow.py +55 -0
- polaris/pkg/flow/base_flow.py +111 -0
- polaris/pkg/flow/impl.py +1277 -0
- polaris/pkg/flow/lossless.py +605 -0
- polaris/pkg/flow/notify.py +46 -0
- polaris/pkg/flow/register.py +210 -0
- polaris/pkg/flow/skill.py +110 -0
- polaris/pkg/flow/sync_flow.py +20 -0
- polaris/pkg/model/__init__.py +15 -0
- polaris/pkg/model/context.py +53 -0
- polaris/pkg/model/contract.py +89 -0
- polaris/pkg/model/core.py +51 -0
- polaris/pkg/model/engine.py +83 -0
- polaris/pkg/model/error.py +94 -0
- polaris/pkg/model/event.py +159 -0
- polaris/pkg/model/lossless.py +102 -0
- polaris/pkg/model/rule.py +152 -0
- polaris/pkg/model/server.py +288 -0
- polaris/pkg/model/service.py +820 -0
- polaris/pkg/model/skill.py +179 -0
- polaris/pkg/model/stat.py +168 -0
- polaris/pkg/model/util.py +58 -0
- polaris/pkg/version/__init__.py +18 -0
- polaris/plugin/__init__.py +32 -0
- polaris/plugin/circuit_breaker/__init__.py +19 -0
- polaris/plugin/circuit_breaker/base.py +54 -0
- polaris/plugin/circuit_breaker/composite.py +265 -0
- polaris/plugin/circuit_breaker/config.py +36 -0
- polaris/plugin/event/__init__.py +21 -0
- polaris/plugin/event/pushgateway.py +202 -0
- polaris/plugin/load_balancer/__init__.py +24 -0
- polaris/plugin/load_balancer/base.py +30 -0
- polaris/plugin/load_balancer/random.py +39 -0
- polaris/plugin/load_balancer/weighted_random.py +50 -0
- polaris/plugin/local_registry/__init__.py +18 -0
- polaris/plugin/local_registry/base.py +47 -0
- polaris/plugin/local_registry/file_cache.py +335 -0
- polaris/plugin/local_registry/local_cache.py +433 -0
- polaris/plugin/server_connector/__init__.py +20 -0
- polaris/plugin/server_connector/base.py +104 -0
- polaris/plugin/server_connector/convert.py +149 -0
- polaris/plugin/server_connector/grpc_connector/__init__.py +21 -0
- polaris/plugin/server_connector/grpc_connector/connection.py +401 -0
- polaris/plugin/server_connector/grpc_connector/grpc_client.py +384 -0
- polaris/plugin/server_connector/grpc_connector/tls.py +45 -0
- polaris/plugin/service_router/__init__.py +28 -0
- polaris/plugin/service_router/base.py +105 -0
- polaris/plugin/service_router/isolated.py +35 -0
- polaris/plugin/service_router/metadata.py +84 -0
- polaris/plugin/service_router/recover.py +62 -0
- polaris/plugin/service_router/util.py +54 -0
- polaris/plugin/skill/__init__.py +16 -0
- polaris/plugin/skill/connector.py +127 -0
- polaris/plugin/skill/persistence.py +94 -0
- polaris/plugin/stat_reporter/__init__.py +16 -0
- polaris/plugin/stat_reporter/base.py +34 -0
- polaris/plugin/stat_reporter/prometheus.py +375 -0
- polaris/plugin_manager.py +146 -0
- polaris_python-0.0.1.dev3.dist-info/METADATA +213 -0
- polaris_python-0.0.1.dev3.dist-info/RECORD +84 -0
- polaris_python-0.0.1.dev3.dist-info/WHEEL +5 -0
- polaris_python-0.0.1.dev3.dist-info/licenses/LICENSE +18 -0
- polaris_python-0.0.1.dev3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,433 @@
|
|
|
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 threading
|
|
18
|
+
|
|
19
|
+
from ...pkg.model.service import ServiceInstances, Instance, new_service_instances, DefaultInstanceLocalValue
|
|
20
|
+
from ...pkg.model.server import InstanceDiscoverRequest
|
|
21
|
+
from ...pkg.model.rule import ServiceRule, new_simple_service_rule
|
|
22
|
+
from ...pkg.model.core import ServiceKey, ServiceEventKey, EventType
|
|
23
|
+
from ...pkg.config.api import get_server_services
|
|
24
|
+
from polaris.plugin_manager import PluginType, register_plugin, InitContext, \
|
|
25
|
+
EventType as PluginEventType
|
|
26
|
+
from .base import BaseLocalRegistry, InstanceFilter
|
|
27
|
+
from ...pkg.flow import impl
|
|
28
|
+
from polaris.pkg.model.error import ErrorCode, SDKError, ServerCode
|
|
29
|
+
import polaris.pkg.model.context as context
|
|
30
|
+
from polaris.pkg.model.event import SDKEventName, SDKEventType, now_event
|
|
31
|
+
from polaris.api.log import get_logger
|
|
32
|
+
from typing import Dict, Any, List
|
|
33
|
+
import time
|
|
34
|
+
|
|
35
|
+
logger = get_logger("cache")
|
|
36
|
+
|
|
37
|
+
PropertyCircuitBreakerStatus = "CircuitBreakerStatus"
|
|
38
|
+
EmptyRule = new_simple_service_rule(EventType.Unknown, None, "")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CacheObject:
|
|
42
|
+
def __init__(self, invalid: bool = False):
|
|
43
|
+
self.invalid: bool = invalid
|
|
44
|
+
self.last_visit_time: float = time.monotonic()
|
|
45
|
+
self.value = None
|
|
46
|
+
self.remote_updated = False
|
|
47
|
+
self.lock = threading.RLock()
|
|
48
|
+
self.load_lock = asyncio.Lock()
|
|
49
|
+
self.loading = False
|
|
50
|
+
|
|
51
|
+
def is_remote_updated(self) -> bool:
|
|
52
|
+
"""Report whether a remote discover response has replaced this snapshot."""
|
|
53
|
+
return self.remote_updated
|
|
54
|
+
|
|
55
|
+
def get_value(self, update_ts: bool):
|
|
56
|
+
if update_ts:
|
|
57
|
+
self.last_visit_time = time.monotonic()
|
|
58
|
+
|
|
59
|
+
return self.value
|
|
60
|
+
|
|
61
|
+
def set_value(self, value):
|
|
62
|
+
self.value = value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InstanceProperties:
|
|
66
|
+
def __init__(self, service: ServiceKey = None, ID: str = "", properties: Dict[str, Any] = None):
|
|
67
|
+
self.service = service
|
|
68
|
+
self.ID = ID
|
|
69
|
+
if properties is None:
|
|
70
|
+
self.properties = {}
|
|
71
|
+
else:
|
|
72
|
+
self.properties = properties
|
|
73
|
+
|
|
74
|
+
def set_id(self, ID):
|
|
75
|
+
self.ID = ID
|
|
76
|
+
|
|
77
|
+
def set_property(self, property, value):
|
|
78
|
+
self.properties[property] = value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ServiceUpdateRequest(ServiceKey):
|
|
82
|
+
def __init__(self, namespace: str = "", service: str = ""):
|
|
83
|
+
super(ServiceUpdateRequest, self).__init__(namespace=namespace, service=service)
|
|
84
|
+
self.properties: List[InstanceProperties] = []
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class InstanceRegistry:
|
|
88
|
+
def get_instances(self, instance_filter: InstanceFilter):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def load_instances(self, service: str, namespace: str):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def update_instances(self, update_request: ServiceUpdateRequest):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def get_services(self):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
# 配置获取的过滤器
|
|
101
|
+
class RuleFilter:
|
|
102
|
+
def __init__(self, service_event_key: ServiceEventKey):
|
|
103
|
+
self.service_event_key = service_event_key
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def load_file_cache(engine, request):
|
|
107
|
+
"""Read one instance backup without using its modification time as a freshness gate."""
|
|
108
|
+
response = await engine.file_cache.async_get(request, expire=None)
|
|
109
|
+
if response is None:
|
|
110
|
+
return None
|
|
111
|
+
# Legacy snapshots did not always carry the response type, but must identify the service.
|
|
112
|
+
if (response.code.value != ServerCode.ExecuteSuccess
|
|
113
|
+
or response.type not in (0, request.type)
|
|
114
|
+
or response.service.name.value != request.name
|
|
115
|
+
or response.service.namespace.value != request.namespace):
|
|
116
|
+
logger.warning("ignore invalid file cache for %s/%s type %s", request.namespace, request.name, request.type)
|
|
117
|
+
return None
|
|
118
|
+
logger.debug("use file cache namespace=%s service=%s", request.namespace, request.name)
|
|
119
|
+
return response
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@register_plugin(PluginType.LOCAL_CACHE, 'inmemory')
|
|
123
|
+
class LocalCache(BaseLocalRegistry):
|
|
124
|
+
def __init__(self, init_ctx: InitContext = None):
|
|
125
|
+
self.init_ctx = init_ctx
|
|
126
|
+
self.config = self.init_ctx.config
|
|
127
|
+
self._cache_lock = threading.RLock()
|
|
128
|
+
self.service_map: Dict[ServiceEventKey, CacheObject] = {}
|
|
129
|
+
self.services: Dict[ServiceEventKey, bool] = {}
|
|
130
|
+
self._watch_counts: Dict[ServiceEventKey, int] = {}
|
|
131
|
+
self._system_services = {
|
|
132
|
+
(cluster.service_key.namespace, cluster.service_key.service)
|
|
133
|
+
for cluster in get_server_services(self.config).values()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def get_instances(self, instance_filter: InstanceFilter) -> ServiceInstances:
|
|
137
|
+
"""
|
|
138
|
+
从缓存中获取实例
|
|
139
|
+
:param instance_filter: 过滤器
|
|
140
|
+
:return: ServiceInstances
|
|
141
|
+
"""
|
|
142
|
+
service_key = ServiceEventKey(instance_filter.namespace, instance_filter.service, EventType.Instance)
|
|
143
|
+
with self._cache_lock:
|
|
144
|
+
cache_obj = self.service_map.get(service_key)
|
|
145
|
+
if cache_obj is None or cache_obj.invalid:
|
|
146
|
+
return None
|
|
147
|
+
value = cache_obj.get_value(not instance_filter.is_internal_request)
|
|
148
|
+
if value is None or (not cache_obj.remote_updated and not instance_filter.include_cache):
|
|
149
|
+
return None
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
async def load_instances(self, service: str, namespace: str, use_discover_cache=True, timeout=None):
|
|
153
|
+
"""
|
|
154
|
+
通过service和namespace加载instance到缓存中
|
|
155
|
+
:param use_discover_cache:
|
|
156
|
+
:param service: 服务名
|
|
157
|
+
:param namespace: 命名空间
|
|
158
|
+
:return: None
|
|
159
|
+
"""
|
|
160
|
+
service_key = ServiceEventKey(namespace, service, EventType.Instance)
|
|
161
|
+
await self._load_instances(service_key, use_discover_cache, timeout)
|
|
162
|
+
|
|
163
|
+
async def _discover(self):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
async def _load_instances(self, service_key: ServiceEventKey, use_discover_cache: bool = True, timeout=None):
|
|
167
|
+
while True:
|
|
168
|
+
with self._cache_lock:
|
|
169
|
+
cache_obj = self.service_map.get(service_key)
|
|
170
|
+
if cache_obj is None:
|
|
171
|
+
cache_obj = CacheObject()
|
|
172
|
+
self.service_map[service_key] = cache_obj
|
|
173
|
+
# Keep loads behind eviction cleanup before creating a new generation.
|
|
174
|
+
async with cache_obj.load_lock:
|
|
175
|
+
if self.service_map.get(service_key) is not cache_obj:
|
|
176
|
+
continue
|
|
177
|
+
if cache_obj.remote_updated and cache_obj.value is not None:
|
|
178
|
+
cache_obj.get_value(True)
|
|
179
|
+
return
|
|
180
|
+
cache_obj.loading = True
|
|
181
|
+
try:
|
|
182
|
+
await self._load_value(service_key, cache_obj, use_discover_cache, timeout)
|
|
183
|
+
cache_obj.get_value(True)
|
|
184
|
+
finally:
|
|
185
|
+
cache_obj.loading = False
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
def _service_instances(self, response, old_instances=None, loaded_from_file=False):
|
|
189
|
+
def create_local_value(instance):
|
|
190
|
+
event_handle_list = self.init_ctx.plugins.get_event_subscriber(PluginEventType.ON_LOCALVALUE_CREATED)
|
|
191
|
+
local_value = DefaultInstanceLocalValue()
|
|
192
|
+
for handle in event_handle_list:
|
|
193
|
+
handle.on_plugin_event(PluginEventType.ON_LOCALVALUE_CREATED, local_value)
|
|
194
|
+
return local_value
|
|
195
|
+
|
|
196
|
+
return ServiceInstances.new_from_discover_rsp(
|
|
197
|
+
response, create_local_value, old_instances=old_instances, loaded_from_file=loaded_from_file,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _publish_resource_event(self, event_name, service_key, old_value, new_value):
|
|
201
|
+
event_center = self.init_ctx.value_ctx.core_map.get(context.ContextKeyEventCenter)
|
|
202
|
+
if event_center is None:
|
|
203
|
+
return
|
|
204
|
+
event_center.publish(now_event(
|
|
205
|
+
self.init_ctx.value_ctx.get_client_id(), SDKEventType.INSTANCE, event_name,
|
|
206
|
+
client_ip=self.config.get_global().get_api().get_bind_ip(),
|
|
207
|
+
namespace=service_key.namespace, service=service_key.service,
|
|
208
|
+
old_value=old_value, new_value=new_value,
|
|
209
|
+
))
|
|
210
|
+
|
|
211
|
+
async def _load_value(self, service_key, cache_obj, use_discover_cache, timeout=None):
|
|
212
|
+
if service_key.event_type != EventType.Instance:
|
|
213
|
+
raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "only instance resources are cached")
|
|
214
|
+
engine = self.init_ctx.value_ctx.get_value(context.ContextKeyEngine)
|
|
215
|
+
update_task = engine.update_task_set.get(service_key)
|
|
216
|
+
if update_task is None or update_task.cache_obj is not cache_obj:
|
|
217
|
+
interval = self.config.get_consumer().get_local_cache().service_refresh_interval
|
|
218
|
+
system = self.config.get_global().get_system()
|
|
219
|
+
for cluster in (system.get_discover_cluster(), system.get_health_check_cluster(),
|
|
220
|
+
system.get_monitor_cluster()):
|
|
221
|
+
if (service_key.namespace, service_key.service) == (cluster.namespace, cluster.service):
|
|
222
|
+
interval = cluster.refresh_interval
|
|
223
|
+
update_task = impl.ServiceUpdateTask(
|
|
224
|
+
service_key, cache_obj, interval,
|
|
225
|
+
int(time.time() * 1000),
|
|
226
|
+
)
|
|
227
|
+
engine.update_task_set[service_key] = update_task
|
|
228
|
+
|
|
229
|
+
req = InstanceDiscoverRequest(1, service_key.service, service_key.namespace)
|
|
230
|
+
old_value = cache_obj.value
|
|
231
|
+
if cache_obj.value is None:
|
|
232
|
+
disk = await load_file_cache(engine, req)
|
|
233
|
+
if disk is not None:
|
|
234
|
+
disk_value = self._service_instances(disk, loaded_from_file=True)
|
|
235
|
+
cache_obj.set_value(disk_value)
|
|
236
|
+
self._publish_resource_event(SDKEventName.ON_SERVICE_ADDED, service_key, None, disk_value)
|
|
237
|
+
old_value = disk_value
|
|
238
|
+
if cache_obj.value is not None:
|
|
239
|
+
req.revision = cache_obj.value.revision
|
|
240
|
+
|
|
241
|
+
client = engine.svc_connector
|
|
242
|
+
success_loaded = False
|
|
243
|
+
err = None
|
|
244
|
+
ret = None
|
|
245
|
+
api_config = self.config.get_global().get_api()
|
|
246
|
+
try:
|
|
247
|
+
# Reconnecting streams may never raise, so bound the entire initial remote wait.
|
|
248
|
+
async with asyncio.timeout((timeout or api_config.get_timeout()) / 1000):
|
|
249
|
+
for attempt in range(api_config.get_max_retry_times() + 1):
|
|
250
|
+
try:
|
|
251
|
+
ret = await client.discover(req, use_discover_cache)
|
|
252
|
+
except SDKError as e:
|
|
253
|
+
err = e
|
|
254
|
+
if e.server_code == ServerCode.NotFoundService:
|
|
255
|
+
raise
|
|
256
|
+
if e.err_code == ErrorCode.ErrCodeDataNotChange and cache_obj.value is not None:
|
|
257
|
+
cache_obj.remote_updated = True
|
|
258
|
+
self.services[service_key] = True
|
|
259
|
+
return
|
|
260
|
+
if e.err_code not in (ErrorCode.ErrCodeNetworkError, ErrorCode.ErrCodeAPITimeoutError):
|
|
261
|
+
raise
|
|
262
|
+
logger.error("Failed to do discover %s and retry: %s", service_key.__dict__, repr(e))
|
|
263
|
+
if attempt < api_config.get_max_retry_times():
|
|
264
|
+
await asyncio.sleep(api_config.get_retry_interval() / 1000)
|
|
265
|
+
continue
|
|
266
|
+
else:
|
|
267
|
+
success_loaded = True
|
|
268
|
+
engine.file_cache.save(req, ret)
|
|
269
|
+
break
|
|
270
|
+
except SDKError as error:
|
|
271
|
+
if error.server_code == ServerCode.NotFoundService:
|
|
272
|
+
await self.remove_service(service_key, cache_obj)
|
|
273
|
+
raise
|
|
274
|
+
except TimeoutError:
|
|
275
|
+
if err is None:
|
|
276
|
+
err = SDKError(ErrorCode.ErrCodeAPITimeoutError,
|
|
277
|
+
"Timed out loading %s/%s type %s" % (req.namespace, req.name, req.type))
|
|
278
|
+
logger.warning("%s", err)
|
|
279
|
+
if not success_loaded:
|
|
280
|
+
if err is None:
|
|
281
|
+
err = SDKError(ErrorCode.ErrCodeAPITimeoutError,
|
|
282
|
+
"Timed out loading %s/%s type %s" % (req.namespace, req.name, req.type))
|
|
283
|
+
raise err
|
|
284
|
+
|
|
285
|
+
new_value = self._service_instances(ret, old_instances=cache_obj.value)
|
|
286
|
+
cache_obj.set_value(new_value)
|
|
287
|
+
cache_obj.remote_updated = True
|
|
288
|
+
self.services[service_key] = True
|
|
289
|
+
log_loaded = logger.info if old_value is None else logger.debug
|
|
290
|
+
log_loaded(
|
|
291
|
+
"cache snapshot loaded namespace=%s service=%s revision=%s instances=%d",
|
|
292
|
+
service_key.namespace, service_key.service,
|
|
293
|
+
new_value.revision, len(new_value.instances),
|
|
294
|
+
)
|
|
295
|
+
self._publish_resource_event(
|
|
296
|
+
SDKEventName.ON_SERVICE_ADDED if old_value is None else SDKEventName.ON_SERVICE_UPDATED,
|
|
297
|
+
service_key, old_value, new_value,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
async def remove_service(self, service_key, cache_obj):
|
|
301
|
+
"""Delete one generation and its backup before allowing a replacement load."""
|
|
302
|
+
if self.service_map.get(service_key) is not cache_obj or cache_obj.invalid:
|
|
303
|
+
return
|
|
304
|
+
cache_obj.invalid = True
|
|
305
|
+
self.services.pop(service_key, None)
|
|
306
|
+
engine = self.init_ctx.value_ctx.get_value(context.ContextKeyEngine)
|
|
307
|
+
try:
|
|
308
|
+
await engine.stop_service_update(service_key, cache_obj)
|
|
309
|
+
finally:
|
|
310
|
+
if self.service_map.get(service_key) is cache_obj:
|
|
311
|
+
self.service_map.pop(service_key)
|
|
312
|
+
self._publish_resource_event(SDKEventName.ON_SERVICE_DELETED, service_key, cache_obj.value, None)
|
|
313
|
+
|
|
314
|
+
def get_service_revision(self, service, namespace, type):
|
|
315
|
+
service_key = ServiceEventKey(namespace, service, type)
|
|
316
|
+
cached_object = self.service_map.get(service_key, None)
|
|
317
|
+
if cached_object is None or cached_object.invalid or cached_object.value is None:
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
return cached_object.value.revision
|
|
321
|
+
|
|
322
|
+
def update_instances(self, update_request: ServiceUpdateRequest):
|
|
323
|
+
"""
|
|
324
|
+
更新实例的本地信息
|
|
325
|
+
:param update_request:
|
|
326
|
+
:return:
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
service_key = ServiceEventKey(update_request.namespace, update_request.service, EventType.Instance)
|
|
330
|
+
cache_obj = self.service_map.get(service_key, None)
|
|
331
|
+
if cache_obj is None:
|
|
332
|
+
raise Exception("cache not exists in update_instances %s" % service_key.__dict__)
|
|
333
|
+
|
|
334
|
+
for p in update_request.properties:
|
|
335
|
+
service_instances = self.get_instances(InstanceFilter(p.service.namespace, p.service.service, True))
|
|
336
|
+
local_value = service_instances.get_instance_local_value(p.ID)
|
|
337
|
+
if local_value is None:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
for k, v in p.properties.items():
|
|
341
|
+
if k == PropertyCircuitBreakerStatus:
|
|
342
|
+
local_value.set_circuit_breaker_status(v)
|
|
343
|
+
|
|
344
|
+
def update_service(self, service_key: ServiceEventKey, service_instance: ServiceInstances):
|
|
345
|
+
cache_object = self.service_map.get(service_key, None)
|
|
346
|
+
if cache_object is None or cache_object.invalid:
|
|
347
|
+
logger.error("updating object that is not exist in cache %s", service_key.__dict__)
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
with cache_object.lock:
|
|
351
|
+
old_value = cache_object.value
|
|
352
|
+
cache_object.set_value(service_instance)
|
|
353
|
+
cache_object.remote_updated = True
|
|
354
|
+
self._publish_resource_event(SDKEventName.ON_SERVICE_UPDATED, service_key, old_value, service_instance)
|
|
355
|
+
self.services[service_key] = True
|
|
356
|
+
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
# 非阻塞获取配置信息
|
|
360
|
+
def get_service_rule(self, rule_filter: RuleFilter) -> ServiceRule:
|
|
361
|
+
with self._cache_lock:
|
|
362
|
+
cache_obj = self.service_map.get(rule_filter.service_event_key)
|
|
363
|
+
if cache_obj is None or cache_obj.invalid:
|
|
364
|
+
return EmptyRule
|
|
365
|
+
return cache_obj.get_value(True) or EmptyRule
|
|
366
|
+
|
|
367
|
+
def get_services(self):
|
|
368
|
+
return self.services.copy()
|
|
369
|
+
|
|
370
|
+
def watch_service(self, namespace: str, service: str):
|
|
371
|
+
"""Pin one instance resource so idle eviction does not remove it."""
|
|
372
|
+
service_key = ServiceEventKey(namespace, service, EventType.Instance)
|
|
373
|
+
with self._cache_lock:
|
|
374
|
+
self._watch_counts[service_key] = self._watch_counts.get(service_key, 0) + 1
|
|
375
|
+
cache_obj = self.service_map.get(service_key)
|
|
376
|
+
if cache_obj is not None:
|
|
377
|
+
cache_obj.get_value(True)
|
|
378
|
+
|
|
379
|
+
def unwatch_service(self, namespace: str, service: str):
|
|
380
|
+
"""Release one watch reference; eviction starts from the release time."""
|
|
381
|
+
service_key = ServiceEventKey(namespace, service, EventType.Instance)
|
|
382
|
+
with self._cache_lock:
|
|
383
|
+
count = self._watch_counts.get(service_key, 0)
|
|
384
|
+
if count <= 1:
|
|
385
|
+
self._watch_counts.pop(service_key, None)
|
|
386
|
+
else:
|
|
387
|
+
self._watch_counts[service_key] = count - 1
|
|
388
|
+
cache_obj = self.service_map.get(service_key)
|
|
389
|
+
if cache_obj is not None:
|
|
390
|
+
cache_obj.last_visit_time = time.monotonic()
|
|
391
|
+
|
|
392
|
+
def _is_expired(self, service_key, cache_obj, now):
|
|
393
|
+
if cache_obj.invalid or cache_obj.loading:
|
|
394
|
+
return False
|
|
395
|
+
if self._watch_counts.get(service_key, 0) > 0:
|
|
396
|
+
return False
|
|
397
|
+
if (service_key.namespace, service_key.service) in self._system_services:
|
|
398
|
+
return False
|
|
399
|
+
expire_time = self.config.get_consumer().get_local_cache().service_expire_time
|
|
400
|
+
return (now - cache_obj.last_visit_time) * 1000 >= expire_time
|
|
401
|
+
|
|
402
|
+
async def expire_services(self):
|
|
403
|
+
"""Release idle resources and their discovery streams on the context owner loop."""
|
|
404
|
+
engine = self.init_ctx.value_ctx.get_value(context.ContextKeyEngine)
|
|
405
|
+
expired = []
|
|
406
|
+
for service_key, cache_obj in list(self.service_map.items()):
|
|
407
|
+
if not self._is_expired(service_key, cache_obj, time.monotonic()):
|
|
408
|
+
continue
|
|
409
|
+
async with cache_obj.load_lock:
|
|
410
|
+
with self._cache_lock:
|
|
411
|
+
if self.service_map.get(service_key) is not cache_obj:
|
|
412
|
+
continue
|
|
413
|
+
if not self._is_expired(service_key, cache_obj, time.monotonic()):
|
|
414
|
+
continue
|
|
415
|
+
cache_obj.invalid = True
|
|
416
|
+
self.services.pop(service_key, None)
|
|
417
|
+
try:
|
|
418
|
+
await engine.stop_service_update(service_key, cache_obj)
|
|
419
|
+
except Exception as error:
|
|
420
|
+
logger.error("failed to clean idle resource %s: %s", service_key.__dict__, error)
|
|
421
|
+
finally:
|
|
422
|
+
with self._cache_lock:
|
|
423
|
+
if self.service_map.get(service_key) is cache_obj:
|
|
424
|
+
self.service_map.pop(service_key)
|
|
425
|
+
self._publish_resource_event(SDKEventName.ON_SERVICE_DELETED, service_key, cache_obj.value, None)
|
|
426
|
+
expired.append(service_key)
|
|
427
|
+
logger.info("evicted idle cache resource %s", service_key.__dict__)
|
|
428
|
+
return expired
|
|
429
|
+
|
|
430
|
+
def destroy(self):
|
|
431
|
+
"""Release this context's memory snapshots after its owner loop has stopped."""
|
|
432
|
+
self.service_map.clear()
|
|
433
|
+
self.services.clear()
|
|
@@ -0,0 +1,20 @@
|
|
|
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 BaseServerConnector
|
|
17
|
+
|
|
18
|
+
from .grpc_connector import GrpcConnector
|
|
19
|
+
|
|
20
|
+
__all__ = ["BaseServerConnector", "GrpcConnector"]
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
|
|
18
|
+
from polarismesh_specification.api.v1.service_manage import response_pb2
|
|
19
|
+
|
|
20
|
+
from polaris.pkg.model.server import (
|
|
21
|
+
InstanceDeRegisterRequest,
|
|
22
|
+
InstanceDiscoverRequest,
|
|
23
|
+
InstanceHeartbeatRequest,
|
|
24
|
+
InstanceRegisterRequest,
|
|
25
|
+
InstanceRegisterResponse,
|
|
26
|
+
ReportClientRequest,
|
|
27
|
+
ReportClientResponse,
|
|
28
|
+
)
|
|
29
|
+
from polaris.pkg.model.service import GetOneInstanceRequest, InstancesResponse
|
|
30
|
+
|
|
31
|
+
__all__ = ["BaseServerConnector"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseServerConnector(ABC):
|
|
35
|
+
"""
|
|
36
|
+
同步注册服务
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def register_instance(self, request: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
同步反注册
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def deregister_instance(self, request: InstanceDeRegisterRequest):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
服务发现
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def discover(
|
|
57
|
+
self,
|
|
58
|
+
request: InstanceDiscoverRequest,
|
|
59
|
+
use_discover_cache: bool = True,
|
|
60
|
+
) -> response_pb2.DiscoverResponse:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
async def stop_discover(self, request: InstanceDiscoverRequest) -> None:
|
|
65
|
+
"""Release discovery state for one resource after its reader has stopped."""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
心跳上报
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
async def heartbeat(self, request: InstanceHeartbeatRequest):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
上报客户端
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def report_client(self, request: ReportClientRequest) -> ReportClientResponse:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
单次服务发现
|
|
86
|
+
"""
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def get_one_instance(self, request: GetOneInstanceRequest) -> InstancesResponse:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def close(self) -> None:
|
|
93
|
+
"""Release connector resources."""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
async def report_service_contract(self, request):
|
|
97
|
+
"""Report a contract when the connector supports the specification RPC."""
|
|
98
|
+
from polaris.pkg.model.error import ErrorCode, SDKError
|
|
99
|
+
raise SDKError(ErrorCode.ErrCodePluginError, "service contract reporting is unsupported by this connector")
|
|
100
|
+
|
|
101
|
+
async def get_service_contract(self, request):
|
|
102
|
+
"""Retrieve a contract when the connector supports the specification RPC."""
|
|
103
|
+
from polaris.pkg.model.error import ErrorCode, SDKError
|
|
104
|
+
raise SDKError(ErrorCode.ErrCodePluginError, "service contract retrieval is unsupported by this connector")
|