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,94 @@
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 typing import Any, Iterable
17
+
18
+
19
+ class ErrorCode:
20
+ # 正常
21
+ ErrCodeSuccess = 0
22
+ # 接口参数错误,API接口调用时,对接口参数进行校验失败后的错误码
23
+ ErrCodeAPIInvalidArgument = 1001
24
+ # 配置参数校验失败
25
+ ErrCodeAPIInvalidConfig = 1002
26
+ # 注册插件、初始化插件、获取插件、销毁插件时发生的错误
27
+ ErrCodePluginError = 1003
28
+ # API调用超时无正确返回
29
+ ErrCodeAPITimeoutError = 1004
30
+ # SDK状态错误,出现在SDK已经销毁还进行API调用
31
+ ErrCodeInvalidStateError = 1005
32
+ # 连接server成功,但是server返回了报错信息
33
+ ErrCodeServerError = 1006
34
+ # 连接服务端出现网络异常
35
+ ErrCodeNetworkError = 1007
36
+ # 服务熔断发生错误
37
+ ErrCodeCircuitBreakerError = 1008
38
+ # 一个实例中包含的信息不合法,如host信息无法正常解析
39
+ ErrCodeInstanceInfoError = 1009
40
+ # 当今行负载均衡、服务路由时,传入的服务没有实例信息
41
+ ErrCodeAPIInstanceNotFound = 1010
42
+ # 路由规则不合法
43
+ ErrCodeInvalidRouteRule = 1011
44
+ # 路由规则无法匹配
45
+ ErrCodeRouteRuleNotMatch = 1012
46
+ # server返回的信息不合法
47
+ ErrCodeInvalidResponse = 1013
48
+ # 获取MaxQueuingTime失败
49
+ ErrCodeMaxQueuingTimeInvalid = 1015
50
+ # server返回的信息不合法
51
+ ErrCodeDataNotChange = 1016
52
+
53
+
54
+ class ServerCode:
55
+ """Polaris response codes used by SDK control flow."""
56
+
57
+ ExecuteSuccess = 200000
58
+ DataNotChange = 200001
59
+ NoNeedUpdate = 200002
60
+ ExistedResource = 400201
61
+ NotFoundResource = 400202
62
+ NotFoundService = 400301
63
+
64
+
65
+ class SDKError(Exception):
66
+ """SDK failure with a stable local code and optional server response code."""
67
+
68
+ def __init__(self, err_code: int, msg: str, server_code: int | None = None):
69
+ super().__init__(err_code, msg)
70
+ self.err_code = err_code
71
+ self.msg = msg
72
+ self.server_code = server_code
73
+
74
+
75
+ def response_code(response: Any) -> int:
76
+ """Return an integer code from a specification response."""
77
+ code = getattr(response, "code", None)
78
+ return int(getattr(code, "value", code or 0))
79
+
80
+
81
+ def response_info(response: Any) -> str:
82
+ """Return response information from a protobuf wrapper."""
83
+ info = getattr(response, "info", None)
84
+ return str(getattr(info, "value", info or "Polaris server request failed"))
85
+
86
+
87
+ def ensure_response(response: Any, accepted: Iterable[int] = (ServerCode.ExecuteSuccess,)) -> Any:
88
+ """Validate a Polaris response and preserve data-not-change semantics."""
89
+ code = response_code(response)
90
+ if code in accepted:
91
+ return response
92
+ if code in {ServerCode.DataNotChange, ServerCode.NoNeedUpdate}:
93
+ raise SDKError(ErrorCode.ErrCodeDataNotChange, response_info(response), server_code=code)
94
+ raise SDKError(ErrorCode.ErrCodeServerError, response_info(response), server_code=code)
@@ -0,0 +1,159 @@
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
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Context-owned event center for capabilities implemented by this SDK."""
18
+
19
+ from dataclasses import dataclass, field
20
+ from threading import RLock
21
+ import time
22
+
23
+
24
+ class SDKEventType:
25
+ """Only event categories backed by implemented runtime behavior."""
26
+
27
+ INSTANCE = "INSTANCE"
28
+ CIRCUIT_BREAKING = "CIRCUIT_BREAKING"
29
+
30
+
31
+ class SDKEventName:
32
+ INSTANCE_THREAD_END = "InstanceThreadEnd"
33
+ ON_SERVICE_ADDED = "OnServiceAdded"
34
+ ON_SERVICE_UPDATED = "OnServiceUpdated"
35
+ ON_SERVICE_DELETED = "OnServiceDeleted"
36
+ CIRCUIT_BREAKER_OPEN = "CircuitBreakerOpen"
37
+ CIRCUIT_BREAKER_HALF_OPEN = "CircuitBreakerHalfOpen"
38
+ CIRCUIT_BREAKER_CLOSE = "CircuitBreakerClose"
39
+
40
+
41
+ class SDKEventStatus:
42
+ OPEN = "OPEN"
43
+ CLOSE = "CLOSE"
44
+ HALF_OPEN = "HALF_OPEN"
45
+
46
+
47
+ # Java EventConstants pairs this SDK can emit. Cache lifecycle names stay local.
48
+ _REMOTE_EVENT_KEYS = frozenset({
49
+ (SDKEventType.INSTANCE, SDKEventName.INSTANCE_THREAD_END),
50
+ (SDKEventType.CIRCUIT_BREAKING, SDKEventName.CIRCUIT_BREAKER_OPEN),
51
+ (SDKEventType.CIRCUIT_BREAKING, SDKEventName.CIRCUIT_BREAKER_HALF_OPEN),
52
+ (SDKEventType.CIRCUIT_BREAKING, SDKEventName.CIRCUIT_BREAKER_CLOSE),
53
+ })
54
+
55
+
56
+ def is_remote_reportable(event) -> bool:
57
+ """Return whether Java EventReporter would accept this event type and name."""
58
+ return (event.event_type, event.event_name) in _REMOTE_EVENT_KEYS
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class SDKEvent:
63
+ """One immutable event emitted by an implemented SDK capability."""
64
+
65
+ event_type: str
66
+ event_name: str
67
+ timestamp: int
68
+ client_id: str
69
+ namespace: str
70
+ service: str
71
+ client_ip: str = ""
72
+ old_value: object = None
73
+ new_value: object = None
74
+ instance_id: str = ""
75
+ host: str = ""
76
+ port: int = 0
77
+ previous_status: str = ""
78
+ current_status: str = ""
79
+ rule_name: str = ""
80
+ reason: str = ""
81
+ additional: dict = field(default_factory=dict)
82
+
83
+ def to_log_message(self):
84
+ """Format an event using the Java and Go logger field order."""
85
+ identity = f"{self.instance_id}@{self.host}:{self.port}" if self.instance_id else ""
86
+ states = f" {self.previous_status}->{self.current_status}" if self.current_status else ""
87
+ return (f"{self.event_type}|{self.event_name}|{self.timestamp}|{self.client_id}|"
88
+ f"{self.client_ip}|{identity}{states}|{self.namespace}|{self.service}|{self.reason}")
89
+
90
+
91
+ class EventCenter:
92
+ """Dispatch context-local events without affecting SDK control flow."""
93
+
94
+ def __init__(self, value_context=None):
95
+ self.value_context = value_context
96
+ self.logger = value_context.logger.get_logger("event") if value_context.logger is not None else None
97
+ self._lock = RLock()
98
+ self._listeners = []
99
+ self.register_listener(self._log_event)
100
+ if self.logger is not None:
101
+ self.logger.info("event center initialized")
102
+
103
+ @staticmethod
104
+ def _listener_name(listener):
105
+ if hasattr(listener, "__name__"):
106
+ return listener.__name__
107
+ return type(listener).__name__
108
+
109
+ @staticmethod
110
+ def _invoke(listener, event):
111
+ method = getattr(listener, "on_sdk_event", None)
112
+ if method is not None:
113
+ return method(event)
114
+ return listener(event)
115
+
116
+ def register_listener(self, listener, event_type: str | None = None):
117
+ """Register a callable or on_sdk_event object, optionally for one event type."""
118
+ if not callable(listener) and not hasattr(listener, "on_sdk_event"):
119
+ raise TypeError("event listener must be callable or implement on_sdk_event")
120
+ with self._lock:
121
+ self._listeners.append((listener, event_type))
122
+
123
+ def unregister_listener(self, listener, event_type: str | None = None):
124
+ """Remove one exact listener registration."""
125
+ with self._lock:
126
+ self._listeners = [(item, subscribed_type)
127
+ for item, subscribed_type in self._listeners
128
+ if item is not listener or subscribed_type != event_type]
129
+
130
+ def publish(self, event: SDKEvent):
131
+ """Notify matching listeners and isolate their exceptions."""
132
+ with self._lock:
133
+ listeners = list(self._listeners)
134
+ for listener, event_type in listeners:
135
+ if event_type is not None and event_type != event.event_type:
136
+ continue
137
+ try:
138
+ self._invoke(listener, event)
139
+ except Exception as error:
140
+ if self.logger is not None:
141
+ self.logger.warning("event listener %s failed: %s", self._listener_name(listener), error)
142
+
143
+ def close(self):
144
+ """Drop listeners when the owning context is destroyed."""
145
+ with self._lock:
146
+ self._listeners.clear()
147
+ if self.logger is not None:
148
+ self.logger.info("event center closed")
149
+
150
+ def _log_event(self, event):
151
+ if self.logger is None:
152
+ return
153
+ self.logger.info("%s", event.to_log_message())
154
+
155
+
156
+ def now_event(client_id: str, event_type: str, event_name: str, **values) -> SDKEvent:
157
+ """Build a timestamped event for internal emitters."""
158
+ return SDKEvent(event_type=event_type, event_name=event_name, timestamp=int(time.time() * 1000),
159
+ client_id=client_id, **values)
@@ -0,0 +1,102 @@
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
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Lossless online/offline models aligned with the Java LosslessPolicy plugin."""
18
+
19
+ from __future__ import annotations
20
+
21
+ from abc import ABC, abstractmethod
22
+ from dataclasses import dataclass, field
23
+ from enum import Enum
24
+ from typing import Any, Dict
25
+
26
+
27
+ class RegisterStatus(Enum):
28
+ """Whether a lossless instance has completed its registration."""
29
+
30
+ UNREGISTERED = "UNREGISTERED"
31
+ REGISTERED = "REGISTERED"
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class LosslessInstance:
36
+ """Identify one provider instance participating in lossless lifecycle."""
37
+
38
+ namespace: str
39
+ service: str
40
+ host: str
41
+ port: int
42
+ metadata: Dict[str, str] = field(default_factory=dict)
43
+
44
+ def __hash__(self):
45
+ return hash((self.namespace, self.service, self.host, self.port))
46
+
47
+ def __eq__(self, other):
48
+ if not isinstance(other, LosslessInstance):
49
+ return NotImplemented
50
+ return (self.namespace, self.service, self.host, self.port) == (
51
+ other.namespace, other.service, other.host, other.port)
52
+
53
+ @classmethod
54
+ def from_register_request(cls, request) -> "LosslessInstance":
55
+ return cls(
56
+ namespace=request.namespace,
57
+ service=request.service,
58
+ host=request.host,
59
+ port=request.port,
60
+ metadata=dict(getattr(request, "metadata", {}) or {}),
61
+ )
62
+
63
+
64
+ class InstanceProperties:
65
+ """Properties a lossless policy may set on the instance before registration."""
66
+
67
+ def __init__(self) -> None:
68
+ self._properties: Dict[str, Any] = {}
69
+
70
+ def set_property(self, key: str, value: Any) -> None:
71
+ self._properties[key] = value
72
+
73
+ def get_property(self, key: str) -> Any:
74
+ return self._properties.get(key)
75
+
76
+ @property
77
+ def properties(self) -> Dict[str, Any]:
78
+ return dict(self._properties)
79
+
80
+
81
+ class LosslessActionProvider(ABC):
82
+ """Callbacks that perform the real register, deregister and health checks."""
83
+
84
+ @abstractmethod
85
+ def get_name(self) -> str:
86
+ """Return the provider name used in logs."""
87
+
88
+ @abstractmethod
89
+ async def do_register(self, instance_properties: InstanceProperties):
90
+ """Register the instance after the lossless-online gate."""
91
+
92
+ @abstractmethod
93
+ async def do_deregister(self):
94
+ """Deregister the instance when lossless offline is triggered."""
95
+
96
+ @abstractmethod
97
+ def is_enable_health_check(self) -> bool:
98
+ """Return True to register after a successful health check."""
99
+
100
+ @abstractmethod
101
+ async def do_health_check(self) -> bool:
102
+ """Return True when the instance is ready to receive traffic."""
@@ -0,0 +1,152 @@
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.model.core import EventType
17
+ from polarismesh_specification.api.v1.service_manage import response_pb2
18
+ from polaris.pkg.model.error import SDKError, ErrorCode
19
+
20
+ from abc import ABCMeta, abstractmethod
21
+
22
+
23
+ # ServiceRule 服务配置通用接口
24
+ class ServiceRule(metaclass=ABCMeta):
25
+ @abstractmethod
26
+ def get_type(self):
27
+ pass
28
+
29
+ @abstractmethod
30
+ def get_value(self):
31
+ pass
32
+
33
+ @abstractmethod
34
+ def is_initialized(self):
35
+ pass
36
+
37
+ @abstractmethod
38
+ def get_revision(self):
39
+ pass
40
+
41
+
42
+ # 通用的服务规则实现
43
+ class SimpleServiceRule(ServiceRule):
44
+ def __init(self, event_type: EventType, value, initialized: bool, revision: str):
45
+ self.event_type = event_type
46
+ self.value = value
47
+ self.initialized = initialized
48
+ self.revision = revision
49
+
50
+ def get_type(self):
51
+ return self.event_type
52
+
53
+ def get_value(self):
54
+ return self.value
55
+
56
+ def is_initialized(self):
57
+ return self.initialized
58
+
59
+ def get_revision(self):
60
+ return self.revision
61
+
62
+
63
+ # 创建通用的服务规则
64
+ def new_simple_service_rule(event_type: EventType, value, revision: str) -> ServiceRule:
65
+ svc_rule = SimpleServiceRule()
66
+ svc_rule.event_type = event_type
67
+ if value is None:
68
+ svc_rule.initialized = False
69
+ return svc_rule
70
+ svc_rule.initialized = True
71
+ svc_rule.value = value
72
+ svc_rule.revision = revision
73
+ return svc_rule
74
+
75
+
76
+ # 获取服务规则请求
77
+ class GetServiceRuleRequest:
78
+ def __init__(self, flow_id: int = 0, namespace: str = "", service: str = "", timeout: int = 0,
79
+ retry_count: int = 0):
80
+ # 可选,流水号,用于跟踪用户的请求,默认0
81
+ self.flow_id = flow_id
82
+ # 命名空间
83
+ self.namespace = namespace
84
+ # 服务名
85
+ self.service = service
86
+ # 可选,仅对同步请求有效,本次查询最大超时信息,可选,默认直接获取全局的超时配置
87
+ self.timeout = timeout
88
+ # 可选,重试次数,默认直接获取全局的超时配置
89
+ self.retry_count = retry_count
90
+
91
+ def get_service(self):
92
+ return self.service
93
+
94
+ def get_namespace(self):
95
+ return self.namespace
96
+
97
+ def get_metadata(self):
98
+ return None
99
+
100
+ def set_timeout(self, duration: int):
101
+ self.timeout = duration
102
+
103
+ def set_retry_count(self, retry_count: int):
104
+ self.retry_count = retry_count
105
+
106
+ def validate(self):
107
+ validate_service_metadata(self)
108
+
109
+
110
+ # 服务规则应答
111
+ class ServiceRuleResponse(ServiceRule):
112
+ def __init__(self, event_type: EventType, value, revision: str):
113
+ self.event_type = event_type
114
+ self.value = value
115
+ self.revision = revision
116
+
117
+ def get_type(self):
118
+ return self.event_type
119
+
120
+ def get_value(self):
121
+ return self.value
122
+
123
+ def is_initialized(self):
124
+ return True
125
+
126
+ def get_revision(self):
127
+ return self.revision
128
+
129
+
130
+ class RoutingRuleInProto:
131
+ @classmethod
132
+ def new_from_discover_rsp(cls, resp: response_pb2.DiscoverResponse) -> ServiceRule:
133
+ if resp is None:
134
+ return new_simple_service_rule(event_type=EventType.Routing, value=None, revision="")
135
+ elif resp.routing is None:
136
+ return new_simple_service_rule(event_type=EventType.Routing, value=None, revision="")
137
+ else:
138
+ return new_simple_service_rule(event_type=EventType.Routing, value=resp.routing,
139
+ revision=str(resp.routing.revision.value))
140
+
141
+
142
+ def validate_service_metadata(svc_meta):
143
+ if len(svc_meta.get_service()) == 0:
144
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "validate_service_metadata: service is empty")
145
+ if len(svc_meta.get_namespace()) == 0:
146
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "validate_service_metadata: namespace is empty")
147
+ metadata = svc_meta.get_metadata()
148
+ if metadata is not None and len(metadata) > 0:
149
+ for k, v in svc_meta.get_metadata():
150
+ if len(k) == 0 or len(v) == 0:
151
+ raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument,
152
+ "validate_service_metadata: metadata has empty key or value")