splight-lib 3.3.6__tar.gz → 4.0.0.dev0__tar.gz
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.
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/PKG-INFO +1 -1
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/setup.py +1 -1
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/component/abstract.py +8 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/component/exceptions.py +4 -5
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/execution.py +25 -2
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/webhook.py +3 -3
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/PKG-INFO +1 -1
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/SOURCES.txt +0 -5
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/requires.txt +0 -4
- splight-lib-3.3.6/splight_lib/client/grpc/client.py +0 -71
- splight-lib-3.3.6/splight_lib/client/grpc/decorators.py +0 -34
- splight-lib-3.3.6/splight_lib/client/grpc/reflector.py +0 -320
- splight-lib-3.3.6/splight_lib/component/log_streamer.py +0 -107
- splight-lib-3.3.6/splight_lib/utils/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/LICENSE.txt +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/README.md +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/pyproject.toml +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/setup.cfg +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/abstract/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/abstract/client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/auth/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/auth/exceptions.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/auth/mac_auth.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/auth/token.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/abstract.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/classmap.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/exceptions.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/local_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/remote_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/abstract.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/builder.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/classmap.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/local_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/database/remote_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/abstract.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/builder.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/local_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/remote_client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/exceptions.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/file_handler.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/filter.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/abstract.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/client.py +0 -0
- {splight-lib-3.3.6/splight_lib/client/grpc → splight-lib-4.0.0.dev0/splight_lib/communication}/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/communication/event_handler.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/component/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/component/spec.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/constants.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/encryption.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/logging/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/logging/_internal.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/logging/component.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/logging/constants.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/logging/logging.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/alert.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/asset.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/attribute.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/base.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/communication.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/component.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/dashboard.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/data_address.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/event.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/exceptions.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/file.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/hub.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/native.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/pipeline.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/query.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/secret.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/models/setpoint.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/restclient/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/restclient/client.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/restclient/exceptions.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/restclient/types.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/settings.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/testing/__init__.py +0 -0
- {splight-lib-3.3.6/splight_lib/communication → splight-lib-4.0.0.dev0/splight_lib/utils}/__init__.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/utils/custom_model.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/utils/hub.py +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/dependency_links.txt +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/not-zip-safe +0 -0
- {splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import traceback
|
|
3
4
|
from functools import partial
|
|
4
5
|
from tempfile import NamedTemporaryFile
|
|
5
6
|
from time import sleep
|
|
@@ -79,6 +80,8 @@ class HealthCheckProcessor:
|
|
|
79
80
|
self._running = True
|
|
80
81
|
while self._running:
|
|
81
82
|
if not self._engine.healthcheck():
|
|
83
|
+
exc = self._engine.get_last_exception()
|
|
84
|
+
self._log_exception(exc)
|
|
82
85
|
self._logger.error(
|
|
83
86
|
"Healthcheck task failed.", tags=LogTags.RUNTIME
|
|
84
87
|
)
|
|
@@ -94,6 +97,11 @@ class HealthCheckProcessor:
|
|
|
94
97
|
def stop(self):
|
|
95
98
|
self._running = False
|
|
96
99
|
|
|
100
|
+
def _log_exception(self, exc: Optional[Exception]):
|
|
101
|
+
if exc:
|
|
102
|
+
message = "".join(traceback.format_tb(exc.__traceback__))
|
|
103
|
+
self._logger.exception(message)
|
|
104
|
+
|
|
97
105
|
|
|
98
106
|
class SplightBaseComponent:
|
|
99
107
|
def __init__(
|
|
@@ -20,7 +20,10 @@ class MissingBindingCallback(Exception):
|
|
|
20
20
|
|
|
21
21
|
class MissingRoutineCallback(Exception):
|
|
22
22
|
def __init__(self, routine_name: str, action: str):
|
|
23
|
-
msg =
|
|
23
|
+
msg = (
|
|
24
|
+
f"Missing method associated with a Routine: {routine_name} "
|
|
25
|
+
f"{action}_handler event"
|
|
26
|
+
)
|
|
24
27
|
super().__init__(msg)
|
|
25
28
|
|
|
26
29
|
|
|
@@ -40,7 +43,3 @@ class MissingCommandCallback(Exception):
|
|
|
40
43
|
def __init__(self, method_name: str):
|
|
41
44
|
msg = f"Missing method {method_name} associated with a Command"
|
|
42
45
|
super().__init__(msg)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class LogsStreamerError(Exception):
|
|
46
|
-
pass
|
|
@@ -6,7 +6,7 @@ from functools import wraps
|
|
|
6
6
|
from subprocess import Popen as DefaultPopen
|
|
7
7
|
from threading import Event, Lock
|
|
8
8
|
from threading import Thread as DefaultThread
|
|
9
|
-
from typing import Any, Callable, List, Tuple, Union
|
|
9
|
+
from typing import Any, Callable, List, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
from splight_lib.abstract.client import AbstractClient
|
|
12
12
|
from splight_lib.logging._internal import LogTags, get_splight_logger
|
|
@@ -240,8 +240,20 @@ class Thread(DefaultThread):
|
|
|
240
240
|
def __init__(self, target: Callable, args: Tuple = (), **kwargs) -> None:
|
|
241
241
|
target = self.store_result(target)
|
|
242
242
|
self.result = Empty()
|
|
243
|
+
self._exc = None
|
|
243
244
|
super().__init__(target=target, args=args, name=target, **kwargs)
|
|
244
245
|
|
|
246
|
+
@property
|
|
247
|
+
def exc(self):
|
|
248
|
+
return self._exc
|
|
249
|
+
|
|
250
|
+
def run(self):
|
|
251
|
+
try:
|
|
252
|
+
super().run()
|
|
253
|
+
except Exception as exc:
|
|
254
|
+
self._exc = exc
|
|
255
|
+
raise exc
|
|
256
|
+
|
|
245
257
|
def store_result(self, func: Callable) -> Callable:
|
|
246
258
|
@wraps(func)
|
|
247
259
|
def wrapper(*args, **kwargs):
|
|
@@ -342,9 +354,20 @@ class ExecutionClient(AbstractClient):
|
|
|
342
354
|
return self._scheduler.unschedule(job)
|
|
343
355
|
|
|
344
356
|
def healthcheck(self):
|
|
345
|
-
|
|
357
|
+
is_alive = all(
|
|
346
358
|
[
|
|
347
359
|
p.is_alive() or p.exit_ok()
|
|
348
360
|
for p in self.processes + self.threads
|
|
349
361
|
]
|
|
350
362
|
)
|
|
363
|
+
return is_alive
|
|
364
|
+
|
|
365
|
+
def get_last_exception(self) -> Optional[Exception]:
|
|
366
|
+
"""Get the last exception thrown in one of the threads.
|
|
367
|
+
It assumes that there is only one thread that crashed
|
|
368
|
+
Also, only works for the thread not the processes.
|
|
369
|
+
"""
|
|
370
|
+
broken_thread = [x.exc for x in self.threads if x.exc]
|
|
371
|
+
if broken_thread:
|
|
372
|
+
return broken_thread[0]
|
|
373
|
+
return None
|
|
@@ -14,7 +14,7 @@ class WebhookEvent(BaseModel):
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class WebhookSettings(BaseSettings):
|
|
17
|
-
|
|
17
|
+
SPLIGHT_WEBHOOK_SECRET: str
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class WebhookClient:
|
|
@@ -38,10 +38,10 @@ class WebhookClient:
|
|
|
38
38
|
|
|
39
39
|
def validate_signature(self, payload: bytes, signature: str) -> bool:
|
|
40
40
|
return HmacSignature.verify_header(
|
|
41
|
-
payload, signature, self._settings.
|
|
41
|
+
payload, signature, self._settings.SPLIGHT_WEBHOOK_SECRET
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
def get_signature(self, payload: bytes) -> str:
|
|
45
|
-
hmac = HmacSignature(secret=self._settings.
|
|
45
|
+
hmac = HmacSignature(secret=self._settings.SPLIGHT_WEBHOOK_SECRET)
|
|
46
46
|
signature = hmac.compute_header_signature(payload)
|
|
47
47
|
return signature
|
|
@@ -41,10 +41,6 @@ splight_lib/client/datalake/abstract.py
|
|
|
41
41
|
splight_lib/client/datalake/builder.py
|
|
42
42
|
splight_lib/client/datalake/local_client.py
|
|
43
43
|
splight_lib/client/datalake/remote_client.py
|
|
44
|
-
splight_lib/client/grpc/__init__.py
|
|
45
|
-
splight_lib/client/grpc/client.py
|
|
46
|
-
splight_lib/client/grpc/decorators.py
|
|
47
|
-
splight_lib/client/grpc/reflector.py
|
|
48
44
|
splight_lib/client/hub/__init__.py
|
|
49
45
|
splight_lib/client/hub/abstract.py
|
|
50
46
|
splight_lib/client/hub/client.py
|
|
@@ -53,7 +49,6 @@ splight_lib/communication/event_handler.py
|
|
|
53
49
|
splight_lib/component/__init__.py
|
|
54
50
|
splight_lib/component/abstract.py
|
|
55
51
|
splight_lib/component/exceptions.py
|
|
56
|
-
splight_lib/component/log_streamer.py
|
|
57
52
|
splight_lib/component/spec.py
|
|
58
53
|
splight_lib/logging/__init__.py
|
|
59
54
|
splight_lib/logging/_internal.py
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
from typing import Callable, Optional, Tuple
|
|
2
|
-
|
|
3
|
-
import grpc
|
|
4
|
-
|
|
5
|
-
from splight_lib.client.grpc.decorators import retry_streaming
|
|
6
|
-
from splight_lib.client.grpc.reflector import GrpcReflectionClient
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class LogsGRPCError(Exception):
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class MissingGRPCService(Exception):
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SplightGRPCClient:
|
|
18
|
-
AUTHORIZATION: str = "authorization"
|
|
19
|
-
_SERVICE_NAME: str = None
|
|
20
|
-
|
|
21
|
-
def __init__(self, grpc_host: str, secure_channel: bool = True):
|
|
22
|
-
if not self._SERVICE_NAME:
|
|
23
|
-
raise MissingGRPCService("Missing parameter service_name")
|
|
24
|
-
|
|
25
|
-
if secure_channel:
|
|
26
|
-
self._channel = grpc.secure_channel(
|
|
27
|
-
grpc_host, grpc.ssl_channel_credentials()
|
|
28
|
-
)
|
|
29
|
-
else:
|
|
30
|
-
self._channel = grpc.insecure_channel(grpc_host)
|
|
31
|
-
|
|
32
|
-
self._reflector = GrpcReflectionClient()
|
|
33
|
-
self._reflector.load_protocols(
|
|
34
|
-
self._channel, symbols=[self._SERVICE_NAME]
|
|
35
|
-
)
|
|
36
|
-
self._stub = self._reflector.service_stub_class(self._SERVICE_NAME)(
|
|
37
|
-
self._channel
|
|
38
|
-
)
|
|
39
|
-
self._auth_header: Optional[Tuple[str, str]] = None
|
|
40
|
-
|
|
41
|
-
def set_authorization_header(self, access_id: str, secret_key: str):
|
|
42
|
-
self._auth_header = (
|
|
43
|
-
SplightGRPCClient.AUTHORIZATION,
|
|
44
|
-
f"Splight {access_id} {secret_key}",
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class LogsGRPCClient(SplightGRPCClient):
|
|
49
|
-
_SERVICE_NAME: str = "LogsService"
|
|
50
|
-
_LOG_ENTRY: str = "LogEntry"
|
|
51
|
-
|
|
52
|
-
def __init__(self, grpc_host: str, secure_channel: bool = True):
|
|
53
|
-
super().__init__(grpc_host, secure_channel=secure_channel)
|
|
54
|
-
self._log_entry = self._reflector.message_class(self._LOG_ENTRY)
|
|
55
|
-
|
|
56
|
-
@retry_streaming(times=5)
|
|
57
|
-
def stream_logs(self, log_generator: Callable, component_id: str):
|
|
58
|
-
try:
|
|
59
|
-
self._stub.stash_log_entry(
|
|
60
|
-
self._parse_log_message(log_generator(), component_id),
|
|
61
|
-
metadata=[self._auth_header],
|
|
62
|
-
)
|
|
63
|
-
except grpc.RpcError as exc:
|
|
64
|
-
raise LogsGRPCError("Unable to stream component logs") from exc
|
|
65
|
-
|
|
66
|
-
def _parse_log_message(self, message_iterator: str, component_id: str):
|
|
67
|
-
for message in message_iterator:
|
|
68
|
-
yield self._log_entry(
|
|
69
|
-
message=message,
|
|
70
|
-
component_id=component_id,
|
|
71
|
-
)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from time import sleep
|
|
2
|
-
from typing import Callable
|
|
3
|
-
|
|
4
|
-
import grpc
|
|
5
|
-
|
|
6
|
-
from splight_lib.logging._internal import get_splight_logger
|
|
7
|
-
|
|
8
|
-
logger = get_splight_logger()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class GRPCRetryError(Exception):
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def retry_streaming(
|
|
16
|
-
times: int = 3, delay: float = 0.5, delay_factor: float = 2
|
|
17
|
-
):
|
|
18
|
-
def decorator(func: Callable):
|
|
19
|
-
def wrapper(*args, **kwargs):
|
|
20
|
-
try:
|
|
21
|
-
func(*args, **kwargs)
|
|
22
|
-
except grpc.RpcError as e:
|
|
23
|
-
logger.error(f"Error: {e}")
|
|
24
|
-
if times == 0:
|
|
25
|
-
raise GRPCRetryError("Max retries exceeded")
|
|
26
|
-
sleep(delay)
|
|
27
|
-
logger.warn(f"Retrying {func.__name__}, {times} retries left")
|
|
28
|
-
retry_streaming(times - 1, delay * delay_factor)(func)(
|
|
29
|
-
*args, **kwargs
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
return wrapper
|
|
33
|
-
|
|
34
|
-
return decorator
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
"""A gRPC reflection client.
|
|
2
|
-
|
|
3
|
-
This module provides a programmatic interface for getting grpc class types
|
|
4
|
-
discovered via gRPC reflection service.
|
|
5
|
-
|
|
6
|
-
See:
|
|
7
|
-
https://grpc.io/ for more information on gRPC in general.
|
|
8
|
-
|
|
9
|
-
https://github.com/grpc/grpc/blob/master/doc/server-reflection.md for more
|
|
10
|
-
information on gRPC service reflection.
|
|
11
|
-
|
|
12
|
-
Code taken from
|
|
13
|
-
https://github.com/sparky8512/yagrc
|
|
14
|
-
"""
|
|
15
|
-
from collections import deque
|
|
16
|
-
|
|
17
|
-
from google.protobuf import descriptor_pb2, descriptor_pool, message_factory
|
|
18
|
-
from google.protobuf.internal import enum_type_wrapper
|
|
19
|
-
from grpc_reflection.v1alpha import reflection_pb2, reflection_pb2_grpc
|
|
20
|
-
|
|
21
|
-
# This is somewhat arbitrary, and is just here to prevent hang in the case of
|
|
22
|
-
# dead network connection that the client side still believes to be open.
|
|
23
|
-
QUERY_TIMEOUT = 10
|
|
24
|
-
"""Max wait time for reflection query completion, in seconds."""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ServiceError(Exception):
|
|
28
|
-
"""Error reported by reflection service."""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def __stub_init__(self, channel):
|
|
32
|
-
"""The __init__ method for service stub classes."""
|
|
33
|
-
for (
|
|
34
|
-
name,
|
|
35
|
-
call,
|
|
36
|
-
full_name,
|
|
37
|
-
out_serializer,
|
|
38
|
-
in_serializer,
|
|
39
|
-
) in self._methods: # pylint: disable=protected-access
|
|
40
|
-
setattr(
|
|
41
|
-
self,
|
|
42
|
-
name,
|
|
43
|
-
getattr(channel, call)(
|
|
44
|
-
full_name,
|
|
45
|
-
request_serializer=out_serializer,
|
|
46
|
-
response_deserializer=in_serializer,
|
|
47
|
-
),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _list_services(stub):
|
|
52
|
-
responses = stub.ServerReflectionInfo(
|
|
53
|
-
iter([reflection_pb2.ServerReflectionRequest(list_services="")]),
|
|
54
|
-
timeout=QUERY_TIMEOUT,
|
|
55
|
-
)
|
|
56
|
-
for response in responses:
|
|
57
|
-
if response.HasField("error_response"):
|
|
58
|
-
raise ServiceError(response.error_response.error_message)
|
|
59
|
-
for service in response.list_services_response.service:
|
|
60
|
-
yield service.name
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def list_services(channel):
|
|
64
|
-
"""Get list of fully qualified service names via reflection.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
channel (grpc.Channel): The RPC channel to use.
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
ServiceError: Reflection service responded with an error.
|
|
71
|
-
grpc.RpcError: Lower level RPC error.
|
|
72
|
-
"""
|
|
73
|
-
stub = reflection_pb2_grpc.ServerReflectionStub(channel)
|
|
74
|
-
return list(_list_services(stub))
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def enum_from_descr(proto):
|
|
78
|
-
"""Get enum class from enum descriptor.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
proto (google.protobuf.descriptor.EnumDescriptor): Enum descriptor.
|
|
82
|
-
"""
|
|
83
|
-
return enum_type_wrapper.EnumTypeWrapper(proto)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class GrpcReflectionEngine:
|
|
87
|
-
"""Implementation class for gRPC reflection client.
|
|
88
|
-
|
|
89
|
-
This class is not meant to be used directly. Instead, use
|
|
90
|
-
`GrpcReflectionClient` or `yagrc.importer.GrpcImporter`
|
|
91
|
-
"""
|
|
92
|
-
|
|
93
|
-
def __init__(self):
|
|
94
|
-
self.methods_by_file = {}
|
|
95
|
-
self.pool = descriptor_pool.DescriptorPool()
|
|
96
|
-
self._factory = message_factory.MessageFactory(self.pool)
|
|
97
|
-
|
|
98
|
-
def load_protocols(self, channel, filenames=None, symbols=None):
|
|
99
|
-
"""Implementation of `GrpcReflectionClient.load_protocols`"""
|
|
100
|
-
stub = reflection_pb2_grpc.ServerReflectionStub(channel)
|
|
101
|
-
|
|
102
|
-
requests = []
|
|
103
|
-
if filenames:
|
|
104
|
-
requests.extend(
|
|
105
|
-
reflection_pb2.ServerReflectionRequest(file_by_filename=name)
|
|
106
|
-
for name in filenames
|
|
107
|
-
)
|
|
108
|
-
if symbols:
|
|
109
|
-
requests.extend(
|
|
110
|
-
reflection_pb2.ServerReflectionRequest(
|
|
111
|
-
file_containing_symbol=symbol
|
|
112
|
-
)
|
|
113
|
-
for symbol in symbols
|
|
114
|
-
)
|
|
115
|
-
if not requests:
|
|
116
|
-
requests.extend(
|
|
117
|
-
reflection_pb2.ServerReflectionRequest(
|
|
118
|
-
file_containing_symbol=name
|
|
119
|
-
)
|
|
120
|
-
for name in _list_services(stub)
|
|
121
|
-
if name != "grpc.reflection.v1alpha.ServerReflection"
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
protos = {}
|
|
125
|
-
traversed = set()
|
|
126
|
-
while requests:
|
|
127
|
-
responses = stub.ServerReflectionInfo(
|
|
128
|
-
iter(requests), timeout=QUERY_TIMEOUT
|
|
129
|
-
)
|
|
130
|
-
deps = set()
|
|
131
|
-
for response in responses:
|
|
132
|
-
if response.HasField("error_response"):
|
|
133
|
-
raise ServiceError(response.error_response.error_message)
|
|
134
|
-
for (
|
|
135
|
-
desc_bytes
|
|
136
|
-
) in response.file_descriptor_response.file_descriptor_proto:
|
|
137
|
-
proto = descriptor_pb2.FileDescriptorProto.FromString( # pylint: disable=no-member
|
|
138
|
-
desc_bytes
|
|
139
|
-
)
|
|
140
|
-
traversed.add(proto.name)
|
|
141
|
-
deps.update(proto.dependency)
|
|
142
|
-
protos[proto.name] = proto
|
|
143
|
-
self.methods_by_file[proto.name] = {
|
|
144
|
-
service.name: service.method
|
|
145
|
-
for service in proto.service
|
|
146
|
-
}
|
|
147
|
-
deps -= traversed
|
|
148
|
-
requests = [
|
|
149
|
-
reflection_pb2.ServerReflectionRequest(file_by_filename=dep)
|
|
150
|
-
for dep in deps
|
|
151
|
-
]
|
|
152
|
-
# prevent unsatisfied deps from looping forever
|
|
153
|
-
traversed.update(deps)
|
|
154
|
-
|
|
155
|
-
names = deque(protos.keys())
|
|
156
|
-
traversed = set()
|
|
157
|
-
while names:
|
|
158
|
-
name = names[0]
|
|
159
|
-
traversed.add(name)
|
|
160
|
-
# raises KeyError if unsatisfied dep:
|
|
161
|
-
proto = protos[name]
|
|
162
|
-
deps = set(proto.dependency) - traversed
|
|
163
|
-
if deps:
|
|
164
|
-
names = deque(x for x in names if x not in deps)
|
|
165
|
-
names.extendleft(deps)
|
|
166
|
-
else:
|
|
167
|
-
del names[0]
|
|
168
|
-
self.pool.Add(proto)
|
|
169
|
-
|
|
170
|
-
return protos.keys()
|
|
171
|
-
|
|
172
|
-
def file_descriptor(self, name):
|
|
173
|
-
"""Get file descriptor for a proto file that has been loaded.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
name (str): The file name of the .proto file, including path.
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
google.protobuf.descriptor.FileDescriptor: The descriptor for the
|
|
180
|
-
specified file.
|
|
181
|
-
|
|
182
|
-
Raises:
|
|
183
|
-
KeyError: File has not been loaded.
|
|
184
|
-
"""
|
|
185
|
-
return self.pool.FindFileByName(name)
|
|
186
|
-
|
|
187
|
-
def message_from_descr(self, proto):
|
|
188
|
-
"""Get message class from message descriptor.
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
proto (google.protobuf.descriptor.Descriptor): Message descriptor.
|
|
192
|
-
"""
|
|
193
|
-
message = self._factory.GetPrototype(proto)
|
|
194
|
-
return message
|
|
195
|
-
|
|
196
|
-
def gen_stub_class(self, service, method_protos):
|
|
197
|
-
"""Get service stub class from service descriptor.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
service (google.protobuf.descriptor.ServiceDescriptor): Service
|
|
201
|
-
descriptor.
|
|
202
|
-
method_protos (iterable): The
|
|
203
|
-
`google.protobuf.descriptor_pb2.MethodDescriptorProto` objects
|
|
204
|
-
for the methods in the service.
|
|
205
|
-
"""
|
|
206
|
-
stub_methods = []
|
|
207
|
-
dep_descrs = []
|
|
208
|
-
for method_proto in method_protos:
|
|
209
|
-
method = service.methods_by_name[method_proto.name]
|
|
210
|
-
channel_call = "_".join(
|
|
211
|
-
"stream" if x else "unary"
|
|
212
|
-
for x in [
|
|
213
|
-
method_proto.client_streaming,
|
|
214
|
-
method_proto.server_streaming,
|
|
215
|
-
]
|
|
216
|
-
)
|
|
217
|
-
dep_descrs.extend([method.input_type, method.output_type])
|
|
218
|
-
stub_methods.append(
|
|
219
|
-
(
|
|
220
|
-
method.name,
|
|
221
|
-
channel_call,
|
|
222
|
-
"/{}/{}".format(service.full_name, method.name),
|
|
223
|
-
self.message_from_descr(
|
|
224
|
-
method.input_type
|
|
225
|
-
).SerializeToString,
|
|
226
|
-
self.message_from_descr(method.output_type).FromString,
|
|
227
|
-
)
|
|
228
|
-
)
|
|
229
|
-
class_name = service.name + "Stub"
|
|
230
|
-
return (
|
|
231
|
-
type(
|
|
232
|
-
class_name,
|
|
233
|
-
(),
|
|
234
|
-
{"_methods": stub_methods, "__init__": __stub_init__},
|
|
235
|
-
),
|
|
236
|
-
dep_descrs,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
class GrpcReflectionClient:
|
|
241
|
-
"""Client to discover protocol types via gRPC service reflection.
|
|
242
|
-
|
|
243
|
-
Protocol files must be loaded via `load_protocols` prior to getting any of
|
|
244
|
-
the class types.
|
|
245
|
-
|
|
246
|
-
Note that for nested types (fields, oneofs, etc) other than nested message
|
|
247
|
-
types, the top-level message type should be requested and the nested types
|
|
248
|
-
can be accessed on it via attribute.
|
|
249
|
-
"""
|
|
250
|
-
|
|
251
|
-
def __init__(self):
|
|
252
|
-
self._engine = GrpcReflectionEngine()
|
|
253
|
-
|
|
254
|
-
def load_protocols(self, channel, filenames=None, symbols=None):
|
|
255
|
-
"""Load a set of proto files to use later for protocol types.
|
|
256
|
-
|
|
257
|
-
Load one or more specified files and/or files containing specified
|
|
258
|
-
symbols, as well as the transitive dependency files of those files.
|
|
259
|
-
|
|
260
|
-
If neither `filenames` nor `symbols` is specified, then files
|
|
261
|
-
containing the symbols for all services advertised by the RPC server,
|
|
262
|
-
other than the reflection service, are loaded.
|
|
263
|
-
|
|
264
|
-
This method may be called multiple times to load additional proto
|
|
265
|
-
files; however, it is not recommended to use channels that point to
|
|
266
|
-
different servers unless it is known that the symbols either do not
|
|
267
|
-
overlap or contain the same exact version across all servers.
|
|
268
|
-
|
|
269
|
-
The caller-supplied channel is only used within the context of this
|
|
270
|
-
method call, so the caller is free to close it afterwards.
|
|
271
|
-
|
|
272
|
-
Args:
|
|
273
|
-
channel (grpc.Channel): The RPC channel to use.
|
|
274
|
-
filenames (iterable[str]): Optional. Proto file names, with path,
|
|
275
|
-
to request.
|
|
276
|
-
symbols (iterable[str]): Optional. Fully qualified symbol names
|
|
277
|
-
for which to request proto files.
|
|
278
|
-
|
|
279
|
-
Raises:
|
|
280
|
-
ServiceError: Reflection service responded with an error.
|
|
281
|
-
grpc.RpcError: Lower level RPC error.
|
|
282
|
-
"""
|
|
283
|
-
return self._engine.load_protocols(channel, filenames, symbols)
|
|
284
|
-
|
|
285
|
-
def service_stub_class(self, name):
|
|
286
|
-
"""Get the stub class for the specified service type.
|
|
287
|
-
|
|
288
|
-
Args:
|
|
289
|
-
name: Fully qualified name of the protocol service.
|
|
290
|
-
|
|
291
|
-
Raises:
|
|
292
|
-
KeyError: File defining service has not been loaded.
|
|
293
|
-
"""
|
|
294
|
-
service = self._engine.pool.FindServiceByName(name)
|
|
295
|
-
methods = self._engine.methods_by_file[service.file.name][service.name]
|
|
296
|
-
return self._engine.gen_stub_class(service, methods)[0]
|
|
297
|
-
|
|
298
|
-
def message_class(self, name):
|
|
299
|
-
"""Get the class for the specified message type.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
name: Fully qualified name of the protocol message.
|
|
303
|
-
|
|
304
|
-
Raises:
|
|
305
|
-
KeyError: File defining message has not been loaded.
|
|
306
|
-
"""
|
|
307
|
-
return self._engine.message_from_descr(
|
|
308
|
-
self._engine.pool.FindMessageTypeByName(name)
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
def enum_class(self, name):
|
|
312
|
-
"""Get the class for the specified enum type.
|
|
313
|
-
|
|
314
|
-
Args:
|
|
315
|
-
name: Fully qualified name of the protocol enum.
|
|
316
|
-
|
|
317
|
-
Raises:
|
|
318
|
-
KeyError: File defining enum has not been loaded.
|
|
319
|
-
"""
|
|
320
|
-
return enum_from_descr(self._engine.pool.FindEnumTypeByName(name))
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import sys
|
|
3
|
-
from queue import Empty, Queue
|
|
4
|
-
from subprocess import Popen
|
|
5
|
-
from threading import Thread
|
|
6
|
-
from typing import Generator, Optional
|
|
7
|
-
|
|
8
|
-
from splight_lib.client.grpc.client import LogsGRPCClient, LogsGRPCError
|
|
9
|
-
from splight_lib.component.exceptions import LogsStreamerError
|
|
10
|
-
from splight_lib.settings import settings
|
|
11
|
-
|
|
12
|
-
LOG_FORMAT = r"^.* \| .* \| .*:\d{2,} \| .* "
|
|
13
|
-
LOG_PATTERN = re.compile(LOG_FORMAT)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ComponentLogsStreamer:
|
|
17
|
-
def __init__(self, process: Popen, component_id: str):
|
|
18
|
-
self._process = process
|
|
19
|
-
self._component_id = component_id
|
|
20
|
-
|
|
21
|
-
try:
|
|
22
|
-
self._client = self._create_client()
|
|
23
|
-
except Exception as exc:
|
|
24
|
-
raise LogsStreamerError(
|
|
25
|
-
"Unable to connect to gRPC server"
|
|
26
|
-
) from exc
|
|
27
|
-
self._logs_entry = self._client._log_entry
|
|
28
|
-
self._thread: Optional[Thread] = None
|
|
29
|
-
self._queue: Optional[Queue] = None
|
|
30
|
-
self._running: bool = False
|
|
31
|
-
|
|
32
|
-
def _create_client(self):
|
|
33
|
-
client = LogsGRPCClient(
|
|
34
|
-
grpc_host=settings.SPLIGHT_GRPC_HOST, secure_channel=True
|
|
35
|
-
)
|
|
36
|
-
client.set_authorization_header(
|
|
37
|
-
access_id=settings.SPLIGHT_ACCESS_ID,
|
|
38
|
-
secret_key=settings.SPLIGHT_SECRET_KEY,
|
|
39
|
-
)
|
|
40
|
-
return client
|
|
41
|
-
|
|
42
|
-
def start(self):
|
|
43
|
-
self._thread = Thread(target=self._consume_logs, daemon=True)
|
|
44
|
-
self._queue = Queue()
|
|
45
|
-
self._running = True
|
|
46
|
-
self._thread.start()
|
|
47
|
-
self._run()
|
|
48
|
-
|
|
49
|
-
def stop(self):
|
|
50
|
-
self._running = False
|
|
51
|
-
self._thread.join(timeout=10)
|
|
52
|
-
self._queue = None
|
|
53
|
-
self._thread = None
|
|
54
|
-
|
|
55
|
-
def _run(self):
|
|
56
|
-
while self._running:
|
|
57
|
-
try:
|
|
58
|
-
self._client.stream_logs(
|
|
59
|
-
self.logs_iterator, self._component_id
|
|
60
|
-
)
|
|
61
|
-
except LogsGRPCError as exc:
|
|
62
|
-
raise LogsStreamerError(
|
|
63
|
-
"Component Logs stream stopped"
|
|
64
|
-
) from exc
|
|
65
|
-
|
|
66
|
-
def _consume_logs(self):
|
|
67
|
-
reader = iter(self._process.stdout.readline, "")
|
|
68
|
-
for new_line in reader:
|
|
69
|
-
if self._process.poll() is not None:
|
|
70
|
-
output, error = self._process.communicate()
|
|
71
|
-
output_msg = output.decode("utf-8")
|
|
72
|
-
error_msg = error.decode("utf-8")
|
|
73
|
-
self._queue.put(output_msg)
|
|
74
|
-
self._queue.put(error_msg)
|
|
75
|
-
self._running = False
|
|
76
|
-
break
|
|
77
|
-
self._queue.put(new_line.decode("utf-8"))
|
|
78
|
-
|
|
79
|
-
def logs_iterator(self) -> Generator:
|
|
80
|
-
self._message_buffer = []
|
|
81
|
-
while True:
|
|
82
|
-
try:
|
|
83
|
-
message = self._queue.get(timeout=10)
|
|
84
|
-
except Empty:
|
|
85
|
-
msg = "".join(self._message_buffer)
|
|
86
|
-
if msg:
|
|
87
|
-
sys.stdout.write(msg)
|
|
88
|
-
yield msg
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
full_msg = self._generate_message(message)
|
|
92
|
-
if not full_msg:
|
|
93
|
-
continue
|
|
94
|
-
yield full_msg
|
|
95
|
-
sys.stdout.write(full_msg)
|
|
96
|
-
|
|
97
|
-
def _generate_message(self, raw_msg: str) -> Optional[str]:
|
|
98
|
-
if self._is_log(raw_msg):
|
|
99
|
-
msg = "".join(self._message_buffer)
|
|
100
|
-
self._message_buffer = [raw_msg]
|
|
101
|
-
return msg
|
|
102
|
-
self._message_buffer.append(raw_msg)
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
def _is_log(self, raw_msg: str) -> bool:
|
|
106
|
-
match = LOG_PATTERN.match(raw_msg)
|
|
107
|
-
return True if match else False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/local_client.py
RENAMED
|
File without changes
|
{splight-lib-3.3.6 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/remote_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{splight-lib-3.3.6/splight_lib/communication → splight-lib-4.0.0.dev0/splight_lib/utils}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|