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
polaris/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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.api.config import (
|
|
17
|
+
SDKContext,
|
|
18
|
+
init_context,
|
|
19
|
+
init_context_by_config,
|
|
20
|
+
init_context_by_file,
|
|
21
|
+
init_context_by_stream,
|
|
22
|
+
)
|
|
23
|
+
from polaris.api.consumer import ConsumerAPI, ServiceWatch, create_consumer_by_config, create_consumer_by_file
|
|
24
|
+
from polaris.api.log import LogOptions
|
|
25
|
+
from polaris.api.lossless import LosslessAPI
|
|
26
|
+
from polaris.api.provider import ProviderAPI, create_provider_by_config, create_provider_by_file
|
|
27
|
+
from polaris.api.skill import (
|
|
28
|
+
SkillAPI,
|
|
29
|
+
create_skill_by_config,
|
|
30
|
+
create_skill_by_context,
|
|
31
|
+
create_skill_by_default_config_file,
|
|
32
|
+
create_skill_by_file,
|
|
33
|
+
)
|
|
34
|
+
from polaris.pkg.model.service import Instance
|
|
35
|
+
from polaris.pkg.model.event import EventCenter, SDKEvent, SDKEventName, SDKEventType
|
|
36
|
+
from polaris.pkg.model.lossless import InstanceProperties, LosslessActionProvider, LosslessInstance, RegisterStatus
|
|
37
|
+
from polaris.pkg.config.api import Configuration
|
|
38
|
+
from polaris.pkg.model.error import ErrorCode, SDKError
|
|
39
|
+
from polaris._version import __version__
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"Configuration",
|
|
43
|
+
"ConsumerAPI",
|
|
44
|
+
"ServiceWatch",
|
|
45
|
+
"ErrorCode",
|
|
46
|
+
"LogOptions",
|
|
47
|
+
"LosslessAPI",
|
|
48
|
+
"LosslessActionProvider",
|
|
49
|
+
"LosslessInstance",
|
|
50
|
+
"InstanceProperties",
|
|
51
|
+
"ProviderAPI",
|
|
52
|
+
"Instance",
|
|
53
|
+
"EventCenter",
|
|
54
|
+
"SDKEvent",
|
|
55
|
+
"SDKEventName",
|
|
56
|
+
"SDKEventType",
|
|
57
|
+
"SkillAPI",
|
|
58
|
+
"RegisterStatus",
|
|
59
|
+
"create_skill_by_config",
|
|
60
|
+
"create_skill_by_context",
|
|
61
|
+
"create_skill_by_default_config_file",
|
|
62
|
+
"create_skill_by_file",
|
|
63
|
+
"SDKContext",
|
|
64
|
+
"SDKError",
|
|
65
|
+
"create_consumer_by_config",
|
|
66
|
+
"create_consumer_by_file",
|
|
67
|
+
"create_provider_by_config",
|
|
68
|
+
"create_provider_by_file",
|
|
69
|
+
"init_context_by_config",
|
|
70
|
+
"init_context_by_file",
|
|
71
|
+
"init_context",
|
|
72
|
+
"init_context_by_stream",
|
|
73
|
+
"__version__",
|
|
74
|
+
]
|
polaris/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
__version__ = "0.0.1.dev3"
|
polaris/api/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
"""Public Polaris SDK APIs."""
|
|
17
|
+
|
|
18
|
+
from polaris.api.config import (
|
|
19
|
+
SDKContext,
|
|
20
|
+
init_context,
|
|
21
|
+
init_context_by_config,
|
|
22
|
+
init_context_by_file,
|
|
23
|
+
init_context_by_stream,
|
|
24
|
+
)
|
|
25
|
+
from polaris.api.consumer import ConsumerAPI, ServiceWatch, create_consumer_by_config, create_consumer_by_file
|
|
26
|
+
from polaris.api.log import LogOptions
|
|
27
|
+
from polaris.api.provider import ProviderAPI, create_provider_by_config, create_provider_by_file
|
|
28
|
+
from polaris.api.skill import (
|
|
29
|
+
SkillAPI,
|
|
30
|
+
create_skill_by_config,
|
|
31
|
+
create_skill_by_context,
|
|
32
|
+
create_skill_by_default_config_file,
|
|
33
|
+
create_skill_by_file,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"ConsumerAPI",
|
|
38
|
+
"ServiceWatch",
|
|
39
|
+
"LogOptions",
|
|
40
|
+
"ProviderAPI",
|
|
41
|
+
"SkillAPI",
|
|
42
|
+
"SDKContext",
|
|
43
|
+
"create_consumer_by_config",
|
|
44
|
+
"create_consumer_by_file",
|
|
45
|
+
"create_provider_by_config",
|
|
46
|
+
"create_provider_by_file",
|
|
47
|
+
"create_skill_by_config",
|
|
48
|
+
"create_skill_by_context",
|
|
49
|
+
"create_skill_by_default_config_file",
|
|
50
|
+
"create_skill_by_file",
|
|
51
|
+
"init_context_by_config",
|
|
52
|
+
"init_context_by_file",
|
|
53
|
+
"init_context",
|
|
54
|
+
"init_context_by_stream",
|
|
55
|
+
]
|
polaris/api/_log_file.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
"""Exclusive file ownership for SDK rotating logs."""
|
|
17
|
+
|
|
18
|
+
import errno
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import stat
|
|
23
|
+
import time
|
|
24
|
+
from logging.handlers import RotatingFileHandler
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from threading import Event, RLock, Thread
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import fcntl
|
|
30
|
+
except ImportError: # pragma: no cover - Windows CI
|
|
31
|
+
fcntl = None
|
|
32
|
+
import msvcrt
|
|
33
|
+
|
|
34
|
+
from polaris.pkg.model.error import ErrorCode, SDKError
|
|
35
|
+
|
|
36
|
+
DEFAULT_MAX_BYTES = 50 * 1024 * 1024
|
|
37
|
+
DEFAULT_BACKUP_COUNT = 5
|
|
38
|
+
DEFAULT_MAX_AGE_DAYS = 30
|
|
39
|
+
CLEANUP_INTERVAL_SECONDS = 3600
|
|
40
|
+
_UUID_PATTERN = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
41
|
+
_maintenance_logger = logging.getLogger("polaris.log.maintenance")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LogFileLock:
|
|
45
|
+
"""Exclusively own a base file and its numbered backup namespace."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, path):
|
|
48
|
+
self.path = Path(path).resolve()
|
|
49
|
+
self.stream = None
|
|
50
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
lock_path = self.path
|
|
52
|
+
# Explicit numbered filenames must not overlap another logger's backups.
|
|
53
|
+
while re.fullmatch(r"\.[1-9][0-9]*", lock_path.suffix):
|
|
54
|
+
lock_path = lock_path.with_suffix("")
|
|
55
|
+
stream = lock_path.with_name(f".{lock_path.name}.lock").open("a+b")
|
|
56
|
+
try:
|
|
57
|
+
if fcntl is not None:
|
|
58
|
+
fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
59
|
+
else: # pragma: no cover - Windows CI
|
|
60
|
+
if os.fstat(stream.fileno()).st_size == 0:
|
|
61
|
+
stream.write(b"\0")
|
|
62
|
+
stream.flush()
|
|
63
|
+
stream.seek(0)
|
|
64
|
+
msvcrt.locking(stream.fileno(), msvcrt.LK_NBLCK, 1)
|
|
65
|
+
except OSError as error:
|
|
66
|
+
stream.close()
|
|
67
|
+
if error.errno in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
|
|
68
|
+
raise SDKError(ErrorCode.ErrCodeAPIInvalidConfig,
|
|
69
|
+
f"Log file is already in use: {self.path}") from error
|
|
70
|
+
raise
|
|
71
|
+
self.stream = stream
|
|
72
|
+
|
|
73
|
+
def close(self):
|
|
74
|
+
"""Release ownership without unlinking the lock inode used by other processes."""
|
|
75
|
+
if self.stream is not None:
|
|
76
|
+
self.stream.close()
|
|
77
|
+
self.stream = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def reserve_log_files(paths, retained=None):
|
|
81
|
+
"""Reserve all category paths atomically, retaining a process logger's own locks."""
|
|
82
|
+
retained = retained or {}
|
|
83
|
+
locks = {}
|
|
84
|
+
acquired = []
|
|
85
|
+
try:
|
|
86
|
+
for path in sorted(set(paths)):
|
|
87
|
+
lock = retained.get(path)
|
|
88
|
+
if lock is None:
|
|
89
|
+
lock = LogFileLock(path)
|
|
90
|
+
acquired.append(lock)
|
|
91
|
+
locks[path] = lock
|
|
92
|
+
except Exception:
|
|
93
|
+
for lock in reversed(acquired):
|
|
94
|
+
lock.close()
|
|
95
|
+
raise
|
|
96
|
+
return locks
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ExclusiveRotatingFileHandler(RotatingFileHandler):
|
|
100
|
+
"""Do not reopen a closed handler or write through a fork-inherited handler."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, *args, max_age_days=DEFAULT_MAX_AGE_DAYS, **kwargs):
|
|
103
|
+
self.owner_pid = os.getpid()
|
|
104
|
+
self.max_age_days = max_age_days
|
|
105
|
+
super().__init__(*args, **kwargs)
|
|
106
|
+
|
|
107
|
+
def emit(self, record):
|
|
108
|
+
if not self._closed and self.owner_pid == os.getpid():
|
|
109
|
+
super().emit(record)
|
|
110
|
+
|
|
111
|
+
def doRollover(self):
|
|
112
|
+
"""Keep numbered backups while recording their age from the rotation time."""
|
|
113
|
+
super().doRollover()
|
|
114
|
+
if self.backupCount > 0:
|
|
115
|
+
try:
|
|
116
|
+
os.utime(f"{self.baseFilename}.1", None)
|
|
117
|
+
except OSError as error:
|
|
118
|
+
_maintenance_logger.warning("Cannot update rotated log age: %s", error)
|
|
119
|
+
cutoff = time.time() - self.max_age_days * 86400 if self.max_age_days else None
|
|
120
|
+
_cleanup_backups(Path(self.baseFilename), cutoff, self.backupCount)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _remove_file(path, cutoff=None):
|
|
124
|
+
try:
|
|
125
|
+
info = path.stat(follow_symlinks=False)
|
|
126
|
+
if stat.S_ISREG(info.st_mode) and (cutoff is None or info.st_mtime < cutoff):
|
|
127
|
+
path.unlink()
|
|
128
|
+
except FileNotFoundError:
|
|
129
|
+
pass
|
|
130
|
+
except OSError as error:
|
|
131
|
+
_maintenance_logger.warning("Cannot clean expired log file %s: %s", path, error)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _cleanup_backups(base, cutoff, backup_count=0):
|
|
135
|
+
pattern = re.compile(re.escape(base.name) + r"\.([1-9][0-9]*)")
|
|
136
|
+
try:
|
|
137
|
+
for path in base.parent.iterdir():
|
|
138
|
+
match = pattern.fullmatch(path.name)
|
|
139
|
+
if match:
|
|
140
|
+
if backup_count > 0 and int(match[1]) > backup_count:
|
|
141
|
+
_remove_file(path)
|
|
142
|
+
elif cutoff is not None:
|
|
143
|
+
_remove_file(path, cutoff)
|
|
144
|
+
except OSError as error:
|
|
145
|
+
_maintenance_logger.warning("Cannot scan log backups for %s: %s", base, error)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class LogRetention:
|
|
149
|
+
"""Clean owned backups and expired, inactive UUID-named context files."""
|
|
150
|
+
|
|
151
|
+
def __init__(self, locks, max_age_days, backup_count, family_paths=()):
|
|
152
|
+
self.locks = locks
|
|
153
|
+
self.max_age_days = max_age_days
|
|
154
|
+
self.backup_count = backup_count
|
|
155
|
+
self.family_paths = tuple(family_paths)
|
|
156
|
+
self.handlers = {}
|
|
157
|
+
self.lock = RLock()
|
|
158
|
+
self.stopping = Event()
|
|
159
|
+
self.thread = None
|
|
160
|
+
self.owner_pid = os.getpid()
|
|
161
|
+
|
|
162
|
+
def add_handler(self, handler):
|
|
163
|
+
"""Serialize lazy handler creation with maintenance of its numbered backups."""
|
|
164
|
+
with self.lock:
|
|
165
|
+
self.handlers[Path(handler.baseFilename)] = handler
|
|
166
|
+
|
|
167
|
+
def _inactive_files(self):
|
|
168
|
+
candidates = set()
|
|
169
|
+
for family in self.family_paths:
|
|
170
|
+
pattern = re.compile("(" + re.escape(family.stem) + "-" + _UUID_PATTERN
|
|
171
|
+
+ re.escape(family.suffix) + r")(?:\.[1-9][0-9]*)?")
|
|
172
|
+
try:
|
|
173
|
+
for path in family.parent.iterdir():
|
|
174
|
+
match = pattern.fullmatch(path.name)
|
|
175
|
+
if match:
|
|
176
|
+
candidates.add(path.with_name(match[1]))
|
|
177
|
+
except FileNotFoundError:
|
|
178
|
+
pass
|
|
179
|
+
except OSError as error:
|
|
180
|
+
_maintenance_logger.warning("Cannot scan context logs in %s: %s", family.parent, error)
|
|
181
|
+
return candidates - self.locks.keys()
|
|
182
|
+
|
|
183
|
+
def cleanup(self):
|
|
184
|
+
"""Skip active owners and symlinks; retry failed deletions on the next scan."""
|
|
185
|
+
if self.owner_pid != os.getpid():
|
|
186
|
+
return
|
|
187
|
+
with self.lock:
|
|
188
|
+
cutoff = time.time() - self.max_age_days * 86400 if self.max_age_days else None
|
|
189
|
+
for path in self.locks:
|
|
190
|
+
handler = self.handlers.get(path)
|
|
191
|
+
if handler is not None:
|
|
192
|
+
handler.acquire()
|
|
193
|
+
try:
|
|
194
|
+
_cleanup_backups(path, cutoff, self.backup_count)
|
|
195
|
+
finally:
|
|
196
|
+
if handler is not None:
|
|
197
|
+
handler.release()
|
|
198
|
+
if cutoff is None:
|
|
199
|
+
return
|
|
200
|
+
for path in self._inactive_files():
|
|
201
|
+
if path.is_symlink():
|
|
202
|
+
continue
|
|
203
|
+
try:
|
|
204
|
+
owner = LogFileLock(path)
|
|
205
|
+
except SDKError:
|
|
206
|
+
continue
|
|
207
|
+
except OSError as error:
|
|
208
|
+
_maintenance_logger.warning("Cannot lock inactive log %s: %s", path, error)
|
|
209
|
+
continue
|
|
210
|
+
try:
|
|
211
|
+
_cleanup_backups(path, cutoff)
|
|
212
|
+
_remove_file(path, cutoff)
|
|
213
|
+
finally:
|
|
214
|
+
owner.close()
|
|
215
|
+
|
|
216
|
+
def start(self):
|
|
217
|
+
"""Clean on startup, then hourly even if no log entries are emitted."""
|
|
218
|
+
self.cleanup()
|
|
219
|
+
if self.max_age_days:
|
|
220
|
+
thread = Thread(target=self._run, name="polaris-log-retention", daemon=True)
|
|
221
|
+
thread.start()
|
|
222
|
+
self.thread = thread
|
|
223
|
+
|
|
224
|
+
def _run(self):
|
|
225
|
+
while not self.stopping.wait(CLEANUP_INTERVAL_SECONDS):
|
|
226
|
+
self.cleanup()
|
|
227
|
+
|
|
228
|
+
def close(self):
|
|
229
|
+
"""Finish maintenance before the caller closes handlers and releases locks."""
|
|
230
|
+
self.stopping.set()
|
|
231
|
+
if self.thread is not None and self.owner_pid == os.getpid():
|
|
232
|
+
self.thread.join()
|
polaris/api/config.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
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 import plugin_manager
|
|
17
|
+
from polaris.pkg.flow.impl import create_flow_engine, BaseEngine
|
|
18
|
+
from polaris.pkg.config.api import Configuration
|
|
19
|
+
from polaris.pkg.model.error import SDKError, ErrorCode
|
|
20
|
+
from polaris.pkg.model import context
|
|
21
|
+
from threading import RLock
|
|
22
|
+
import copy
|
|
23
|
+
|
|
24
|
+
from polaris.api.log import ContextLogger, LogOptions, use_context_logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SDKContext(object):
|
|
28
|
+
"""Own the configuration, plugins and runtime for an independent SDK client."""
|
|
29
|
+
def __init__(self, config: Configuration, plugins: plugin_manager.PluginManager, engine: BaseEngine,
|
|
30
|
+
value_context: context.ValueContext = None, destroyed: int = 0):
|
|
31
|
+
self.config = config
|
|
32
|
+
self.plugins = plugins
|
|
33
|
+
self.engine = engine
|
|
34
|
+
self.value_context = value_context if value_context is not None else context.new_value_context()
|
|
35
|
+
self._destroy_lock = RLock()
|
|
36
|
+
# 标识是否已经销毁,0未销毁,1已销毁
|
|
37
|
+
self.destroyed = destroyed
|
|
38
|
+
self._cleaned_up = False
|
|
39
|
+
if context.ContextKeyEventCenter not in self.value_context.core_map:
|
|
40
|
+
from polaris.pkg.model.event import EventCenter
|
|
41
|
+
self.value_context.set_value(context.ContextKeyEventCenter, EventCenter(self.value_context))
|
|
42
|
+
add_callback = getattr(self.engine, "add_destroy_callback", None)
|
|
43
|
+
if add_callback is not None:
|
|
44
|
+
add_callback(self._cleanup)
|
|
45
|
+
|
|
46
|
+
def _cleanup(self):
|
|
47
|
+
if self._cleaned_up:
|
|
48
|
+
return
|
|
49
|
+
self._cleaned_up = True
|
|
50
|
+
try:
|
|
51
|
+
event_center = self.get_event_center()
|
|
52
|
+
if event_center is not None:
|
|
53
|
+
event_center.close()
|
|
54
|
+
self.plugins.destroy_plugins()
|
|
55
|
+
finally:
|
|
56
|
+
self.value_context.clear()
|
|
57
|
+
if self.value_context.logger is not None:
|
|
58
|
+
self.value_context.logger.close()
|
|
59
|
+
|
|
60
|
+
def destroy(self):
|
|
61
|
+
"""Release only this context's resources; repeated destruction is harmless."""
|
|
62
|
+
with self._destroy_lock:
|
|
63
|
+
if self.is_destroy():
|
|
64
|
+
return
|
|
65
|
+
self.destroyed = 1
|
|
66
|
+
try:
|
|
67
|
+
self.engine.destroy()
|
|
68
|
+
finally:
|
|
69
|
+
if getattr(self.engine, "add_destroy_callback", None) is None:
|
|
70
|
+
self._cleanup()
|
|
71
|
+
|
|
72
|
+
def is_destroy(self):
|
|
73
|
+
"""Report whether this client is no longer available."""
|
|
74
|
+
return self.destroyed > 0
|
|
75
|
+
|
|
76
|
+
def get_config(self):
|
|
77
|
+
"""Return the configuration belonging to this client."""
|
|
78
|
+
return self.config
|
|
79
|
+
|
|
80
|
+
def get_plugins(self):
|
|
81
|
+
"""Return this client's plugin manager."""
|
|
82
|
+
return self.plugins
|
|
83
|
+
|
|
84
|
+
def get_engine(self):
|
|
85
|
+
"""Return the runtime owned by this client."""
|
|
86
|
+
return self.engine
|
|
87
|
+
|
|
88
|
+
def get_value_context(self):
|
|
89
|
+
"""Return this client's identity and runtime values."""
|
|
90
|
+
return self.value_context
|
|
91
|
+
|
|
92
|
+
def get_client_id(self) -> str:
|
|
93
|
+
"""Return the unique, stable client ID sent to Polaris."""
|
|
94
|
+
return self.value_context.get_client_id()
|
|
95
|
+
|
|
96
|
+
def get_event_center(self):
|
|
97
|
+
"""Return this context's isolated event center."""
|
|
98
|
+
return self.value_context.core_map.get(context.ContextKeyEventCenter)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def init_context_by_file(path: str, apply_env: bool = True, *, log_options: LogOptions | None = None):
|
|
102
|
+
"""Initialize an SDK context from a YAML file."""
|
|
103
|
+
return init_context_by_config(Configuration.from_file(path, apply_env=apply_env), log_options=log_options)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def init_context_by_stream(buf, apply_env: bool = True, *, log_options: LogOptions | None = None):
|
|
107
|
+
"""Initialize an SDK context from YAML text or bytes."""
|
|
108
|
+
from polaris.pkg.config.loader import build_configuration, parse_config_stream
|
|
109
|
+
return init_context_by_config(
|
|
110
|
+
build_configuration(parse_config_stream(buf), apply_env=apply_env), log_options=log_options,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def init_context(*, log_options: LogOptions | None = None):
|
|
115
|
+
"""Initialize an SDK context from the default file or environment."""
|
|
116
|
+
from polaris.pkg.config.loader import find_default_config_file
|
|
117
|
+
|
|
118
|
+
config_file = find_default_config_file()
|
|
119
|
+
if config_file is not None:
|
|
120
|
+
return init_context_by_file(str(config_file), log_options=log_options)
|
|
121
|
+
return init_context_by_config(Configuration.from_env(), log_options=log_options)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def init_context_by_config(config: Configuration, *, log_options: LogOptions | None = None):
|
|
125
|
+
"""Initialize from assigned configuration values without loading files or environment overrides."""
|
|
126
|
+
config = copy.deepcopy(config)
|
|
127
|
+
config.set_default()
|
|
128
|
+
config.verify()
|
|
129
|
+
global_ctx = context.new_value_context()
|
|
130
|
+
global_ctx.logger = ContextLogger(global_ctx.get_client_id(), log_options)
|
|
131
|
+
from polaris.pkg.model.event import EventCenter
|
|
132
|
+
event_center = EventCenter(global_ctx)
|
|
133
|
+
global_ctx.set_value(context.ContextKeyEventCenter, event_center)
|
|
134
|
+
plugins = plugin_manager.DefaultPluginManager.new_context_manager()
|
|
135
|
+
try:
|
|
136
|
+
with use_context_logger(global_ctx.logger):
|
|
137
|
+
engine = create_flow_engine(config, plugins, global_ctx)
|
|
138
|
+
except Exception:
|
|
139
|
+
try:
|
|
140
|
+
plugins.destroy_plugins()
|
|
141
|
+
finally:
|
|
142
|
+
global_ctx.clear()
|
|
143
|
+
global_ctx.logger.close()
|
|
144
|
+
raise
|
|
145
|
+
return SDKContext(config=config, plugins=plugins, engine=engine, value_context=global_ctx)
|