opengris-scaler 1.12.28__cp313-cp313-musllinux_1_2_x86_64.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.
Potentially problematic release.
This version of opengris-scaler might be problematic. Click here for more details.
- opengris_scaler-1.12.28.dist-info/METADATA +728 -0
- opengris_scaler-1.12.28.dist-info/RECORD +187 -0
- opengris_scaler-1.12.28.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.28.dist-info/entry_points.txt +10 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.28.dist-info/licenses/NOTICE +8 -0
- opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
- opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
- opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
- opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
- scaler/__init__.py +14 -0
- scaler/about.py +5 -0
- scaler/client/__init__.py +0 -0
- scaler/client/agent/__init__.py +0 -0
- scaler/client/agent/client_agent.py +210 -0
- scaler/client/agent/disconnect_manager.py +27 -0
- scaler/client/agent/future_manager.py +112 -0
- scaler/client/agent/heartbeat_manager.py +74 -0
- scaler/client/agent/mixins.py +89 -0
- scaler/client/agent/object_manager.py +98 -0
- scaler/client/agent/task_manager.py +64 -0
- scaler/client/client.py +658 -0
- scaler/client/future.py +252 -0
- scaler/client/object_buffer.py +129 -0
- scaler/client/object_reference.py +25 -0
- scaler/client/serializer/__init__.py +0 -0
- scaler/client/serializer/default.py +16 -0
- scaler/client/serializer/mixins.py +38 -0
- scaler/cluster/__init__.py +0 -0
- scaler/cluster/cluster.py +115 -0
- scaler/cluster/combo.py +150 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +86 -0
- scaler/config/__init__.py +0 -0
- scaler/config/defaults.py +94 -0
- scaler/config/loader.py +96 -0
- scaler/config/mixins.py +20 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +55 -0
- scaler/config/section/ecs_worker_adapter.py +85 -0
- scaler/config/section/native_worker_adapter.py +43 -0
- scaler/config/section/object_storage_server.py +8 -0
- scaler/config/section/scheduler.py +54 -0
- scaler/config/section/symphony_worker_adapter.py +47 -0
- scaler/config/section/top.py +13 -0
- scaler/config/section/webui.py +21 -0
- scaler/config/types/__init__.py +0 -0
- scaler/config/types/network_backend.py +12 -0
- scaler/config/types/object_storage_server.py +45 -0
- scaler/config/types/worker.py +62 -0
- scaler/config/types/zmq.py +83 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +133 -0
- scaler/entry_points/object_storage_server.py +45 -0
- scaler/entry_points/scheduler.py +144 -0
- scaler/entry_points/top.py +286 -0
- scaler/entry_points/webui.py +48 -0
- scaler/entry_points/worker_adapter_ecs.py +191 -0
- scaler/entry_points/worker_adapter_native.py +137 -0
- scaler/entry_points/worker_adapter_symphony.py +98 -0
- scaler/io/__init__.py +0 -0
- scaler/io/async_binder.py +89 -0
- scaler/io/async_connector.py +95 -0
- scaler/io/async_object_storage_connector.py +225 -0
- scaler/io/mixins.py +154 -0
- scaler/io/sync_connector.py +68 -0
- scaler/io/sync_object_storage_connector.py +247 -0
- scaler/io/sync_subscriber.py +83 -0
- scaler/io/utility.py +80 -0
- scaler/io/ymq/__init__.py +0 -0
- scaler/io/ymq/_ymq.pyi +95 -0
- scaler/io/ymq/ymq.py +138 -0
- scaler/io/ymq_async_object_storage_connector.py +184 -0
- scaler/io/ymq_sync_object_storage_connector.py +184 -0
- scaler/object_storage/__init__.py +0 -0
- scaler/protocol/__init__.py +0 -0
- scaler/protocol/capnp/__init__.py +0 -0
- scaler/protocol/capnp/_python.py +6 -0
- scaler/protocol/capnp/common.capnp +68 -0
- scaler/protocol/capnp/message.capnp +218 -0
- scaler/protocol/capnp/object_storage.capnp +57 -0
- scaler/protocol/capnp/status.capnp +73 -0
- scaler/protocol/introduction.md +105 -0
- scaler/protocol/python/__init__.py +0 -0
- scaler/protocol/python/common.py +140 -0
- scaler/protocol/python/message.py +751 -0
- scaler/protocol/python/mixins.py +13 -0
- scaler/protocol/python/object_storage.py +118 -0
- scaler/protocol/python/status.py +279 -0
- scaler/protocol/worker.md +228 -0
- scaler/scheduler/__init__.py +0 -0
- scaler/scheduler/allocate_policy/__init__.py +0 -0
- scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
- scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
- scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
- scaler/scheduler/allocate_policy/mixins.py +55 -0
- scaler/scheduler/controllers/__init__.py +0 -0
- scaler/scheduler/controllers/balance_controller.py +65 -0
- scaler/scheduler/controllers/client_controller.py +131 -0
- scaler/scheduler/controllers/config_controller.py +31 -0
- scaler/scheduler/controllers/graph_controller.py +424 -0
- scaler/scheduler/controllers/information_controller.py +81 -0
- scaler/scheduler/controllers/mixins.py +194 -0
- scaler/scheduler/controllers/object_controller.py +147 -0
- scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
- scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
- scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
- scaler/scheduler/controllers/scaling_policies/null.py +14 -0
- scaler/scheduler/controllers/scaling_policies/types.py +9 -0
- scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
- scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
- scaler/scheduler/controllers/task_controller.py +376 -0
- scaler/scheduler/controllers/worker_controller.py +169 -0
- scaler/scheduler/object_usage/__init__.py +0 -0
- scaler/scheduler/object_usage/object_tracker.py +131 -0
- scaler/scheduler/scheduler.py +251 -0
- scaler/scheduler/task/__init__.py +0 -0
- scaler/scheduler/task/task_state_machine.py +92 -0
- scaler/scheduler/task/task_state_manager.py +61 -0
- scaler/ui/__init__.py +0 -0
- scaler/ui/constants.py +9 -0
- scaler/ui/live_display.py +147 -0
- scaler/ui/memory_window.py +146 -0
- scaler/ui/setting_page.py +40 -0
- scaler/ui/task_graph.py +832 -0
- scaler/ui/task_log.py +107 -0
- scaler/ui/utility.py +66 -0
- scaler/ui/webui.py +147 -0
- scaler/ui/worker_processors.py +104 -0
- scaler/utility/__init__.py +0 -0
- scaler/utility/debug.py +19 -0
- scaler/utility/event_list.py +63 -0
- scaler/utility/event_loop.py +58 -0
- scaler/utility/exceptions.py +42 -0
- scaler/utility/formatter.py +44 -0
- scaler/utility/graph/__init__.py +0 -0
- scaler/utility/graph/optimization.py +27 -0
- scaler/utility/graph/topological_sorter.py +11 -0
- scaler/utility/graph/topological_sorter_graphblas.py +174 -0
- scaler/utility/identifiers.py +107 -0
- scaler/utility/logging/__init__.py +0 -0
- scaler/utility/logging/decorators.py +25 -0
- scaler/utility/logging/scoped_logger.py +33 -0
- scaler/utility/logging/utility.py +183 -0
- scaler/utility/many_to_many_dict.py +123 -0
- scaler/utility/metadata/__init__.py +0 -0
- scaler/utility/metadata/profile_result.py +31 -0
- scaler/utility/metadata/task_flags.py +30 -0
- scaler/utility/mixins.py +13 -0
- scaler/utility/network_util.py +7 -0
- scaler/utility/one_to_many_dict.py +72 -0
- scaler/utility/queues/__init__.py +0 -0
- scaler/utility/queues/async_indexed_queue.py +37 -0
- scaler/utility/queues/async_priority_queue.py +70 -0
- scaler/utility/queues/async_sorted_priority_queue.py +45 -0
- scaler/utility/queues/indexed_queue.py +114 -0
- scaler/utility/serialization.py +9 -0
- scaler/version.txt +1 -0
- scaler/worker/__init__.py +0 -0
- scaler/worker/agent/__init__.py +0 -0
- scaler/worker/agent/heartbeat_manager.py +107 -0
- scaler/worker/agent/mixins.py +137 -0
- scaler/worker/agent/processor/__init__.py +0 -0
- scaler/worker/agent/processor/object_cache.py +107 -0
- scaler/worker/agent/processor/processor.py +285 -0
- scaler/worker/agent/processor/streaming_buffer.py +28 -0
- scaler/worker/agent/processor_holder.py +147 -0
- scaler/worker/agent/processor_manager.py +369 -0
- scaler/worker/agent/profiling_manager.py +109 -0
- scaler/worker/agent/task_manager.py +150 -0
- scaler/worker/agent/timeout_manager.py +19 -0
- scaler/worker/preload.py +84 -0
- scaler/worker/worker.py +265 -0
- scaler/worker_adapter/__init__.py +0 -0
- scaler/worker_adapter/common.py +26 -0
- scaler/worker_adapter/ecs.py +269 -0
- scaler/worker_adapter/native.py +155 -0
- scaler/worker_adapter/symphony/__init__.py +0 -0
- scaler/worker_adapter/symphony/callback.py +45 -0
- scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
- scaler/worker_adapter/symphony/message.py +24 -0
- scaler/worker_adapter/symphony/task_manager.py +289 -0
- scaler/worker_adapter/symphony/worker.py +204 -0
- scaler/worker_adapter/symphony/worker_adapter.py +139 -0
- src/scaler/io/ymq/_ymq.so +0 -0
- src/scaler/object_storage/object_storage_server.so +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
|
|
5
|
+
from scaler.config.loader import load_config
|
|
6
|
+
from scaler.config.section.native_worker_adapter import NativeWorkerAdapterConfig
|
|
7
|
+
from scaler.utility.event_loop import EventLoopType, register_event_loop
|
|
8
|
+
from scaler.utility.logging.utility import setup_logger
|
|
9
|
+
from scaler.worker_adapter.native import NativeWorkerAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_args():
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
"scaler_native_worker_adapter", formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
parser.add_argument("--config", "-c", type=str, default=None, help="Path to the TOML configuration file.")
|
|
18
|
+
|
|
19
|
+
# Server configuration
|
|
20
|
+
parser.add_argument("--adapter-web-host", type=str, help="Host for the native worker adapter HTTP server.")
|
|
21
|
+
parser.add_argument("--adapter-web-port", "-p", type=int, help="Port for the native worker adapter HTTP server.")
|
|
22
|
+
|
|
23
|
+
# Worker configuration
|
|
24
|
+
parser.add_argument("--io-threads", type=int, help="number of io threads for zmq")
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--per-worker-capabilities",
|
|
27
|
+
"-pwc",
|
|
28
|
+
type=str,
|
|
29
|
+
help='comma-separated capabilities provided by the workers (e.g. "-pwc linux,cpu=4")',
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument("--worker-task-queue-size", "-wtqs", type=int, default=10, help="specify worker queue size")
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--max-workers", "-mw", type=int, help="maximum number of workers that can be started, -1 means no limit"
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--heartbeat-interval", "-hi", type=int, help="number of seconds that worker agent send heartbeat to scheduler"
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--task-timeout-seconds", "-tt", type=int, help="default task timeout seconds, 0 means never timeout"
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--death-timeout-seconds",
|
|
43
|
+
"-dt",
|
|
44
|
+
type=int,
|
|
45
|
+
help="number of seconds without scheduler contact before worker shuts down",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--garbage-collect-interval-seconds", "-gc", type=int, help="number of seconds worker doing garbage collection"
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--trim-memory-threshold-bytes",
|
|
52
|
+
"-tm",
|
|
53
|
+
type=int,
|
|
54
|
+
help="number of bytes threshold for worker process that trigger deep garbage collection",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--hard-processor-suspend",
|
|
58
|
+
"-hps",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="if true, suspended worker's processors will be actively suspended with a SIGTSTP signal",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument("--event-loop", "-e", choices=EventLoopType.allowed_types(), help="select event loop type")
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--logging-paths",
|
|
65
|
+
"-lp",
|
|
66
|
+
nargs="*",
|
|
67
|
+
type=str,
|
|
68
|
+
help="specify where worker logs should be logged to, it can accept multiple files, default is /dev/stdout",
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--logging-level",
|
|
72
|
+
"-ll",
|
|
73
|
+
type=str,
|
|
74
|
+
choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
|
|
75
|
+
help="specify the logging level",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--logging-config-file",
|
|
79
|
+
"-lc",
|
|
80
|
+
type=str,
|
|
81
|
+
help="use standard python .conf file to specify python logging file configuration format",
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--object-storage-address",
|
|
85
|
+
"-osa",
|
|
86
|
+
type=str,
|
|
87
|
+
help="specify the object storage server address, e.g.: tcp://localhost:2346",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"scheduler_address",
|
|
91
|
+
nargs="?",
|
|
92
|
+
type=str,
|
|
93
|
+
help="scheduler address to connect workers to, e.g.: `tcp://localhost:6378",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return parser.parse_args()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
args = get_args()
|
|
101
|
+
native_adapter_config = load_config(
|
|
102
|
+
NativeWorkerAdapterConfig, args.config, args, section_name="native_worker_adapter"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
register_event_loop(native_adapter_config.event_loop)
|
|
106
|
+
|
|
107
|
+
setup_logger(
|
|
108
|
+
native_adapter_config.logging_paths,
|
|
109
|
+
native_adapter_config.logging_config_file,
|
|
110
|
+
native_adapter_config.logging_level,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
native_worker_adapter = NativeWorkerAdapter(
|
|
114
|
+
address=native_adapter_config.scheduler_address,
|
|
115
|
+
object_storage_address=native_adapter_config.object_storage_address,
|
|
116
|
+
capabilities=native_adapter_config.per_worker_capabilities.capabilities,
|
|
117
|
+
io_threads=native_adapter_config.io_threads,
|
|
118
|
+
task_queue_size=native_adapter_config.worker_task_queue_size,
|
|
119
|
+
max_workers=native_adapter_config.max_workers,
|
|
120
|
+
heartbeat_interval_seconds=native_adapter_config.heartbeat_interval_seconds,
|
|
121
|
+
task_timeout_seconds=native_adapter_config.task_timeout_seconds,
|
|
122
|
+
death_timeout_seconds=native_adapter_config.death_timeout_seconds,
|
|
123
|
+
garbage_collect_interval_seconds=native_adapter_config.garbage_collect_interval_seconds,
|
|
124
|
+
trim_memory_threshold_bytes=native_adapter_config.trim_memory_threshold_bytes,
|
|
125
|
+
hard_processor_suspend=native_adapter_config.hard_processor_suspend,
|
|
126
|
+
event_loop=native_adapter_config.event_loop,
|
|
127
|
+
logging_paths=native_adapter_config.logging_paths,
|
|
128
|
+
logging_level=native_adapter_config.logging_level,
|
|
129
|
+
logging_config_file=native_adapter_config.logging_config_file,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
app = native_worker_adapter.create_app()
|
|
133
|
+
web.run_app(app, host=native_adapter_config.adapter_web_host, port=native_adapter_config.adapter_web_port)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
|
|
5
|
+
from scaler.config.loader import load_config
|
|
6
|
+
from scaler.config.section.symphony_worker_adapter import SymphonyWorkerConfig
|
|
7
|
+
from scaler.utility.event_loop import EventLoopType, register_event_loop
|
|
8
|
+
from scaler.utility.logging.utility import setup_logger
|
|
9
|
+
from scaler.worker_adapter.symphony.worker_adapter import SymphonyWorkerAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_args():
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
"scaler Symphony worker adapter", formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
15
|
+
)
|
|
16
|
+
parser.add_argument("--config", "-c", type=str, default=None, help="Path to the TOML configuration file.")
|
|
17
|
+
|
|
18
|
+
# Server configuration
|
|
19
|
+
parser.add_argument("--adapter-web-host", type=str, help="host address for symphony worker adapter HTTP server")
|
|
20
|
+
parser.add_argument("--adapter-web-port", "-p", type=int, help="port for symphony worker adapter HTTP server")
|
|
21
|
+
|
|
22
|
+
# Symphony configuration
|
|
23
|
+
parser.add_argument("--service-name", "-sn", type=str, help="symphony service name")
|
|
24
|
+
parser.add_argument("--base-concurrency", "-n", type=int, help="base task concurrency")
|
|
25
|
+
|
|
26
|
+
# Worker configuration
|
|
27
|
+
parser.add_argument("--io-threads", "-it", help="specify number of io threads per worker")
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--worker-capabilities",
|
|
30
|
+
"-wc",
|
|
31
|
+
type=str,
|
|
32
|
+
help='comma-separated capabilities provided by the worker (e.g. "-wr linux,cpu=4")',
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument("--worker-task-queue-size", "-wtqs", type=int, help="specify symphony worker queue size")
|
|
35
|
+
parser.add_argument("--heartbeat-interval", "-hi", type=int, help="number of seconds to send heartbeat interval")
|
|
36
|
+
parser.add_argument("--death-timeout-seconds", "-ds", type=int, help="death timeout seconds")
|
|
37
|
+
parser.add_argument("--event-loop", "-el", choices=EventLoopType.allowed_types(), help="select event loop type")
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--logging-paths",
|
|
40
|
+
"-lp",
|
|
41
|
+
nargs="*",
|
|
42
|
+
type=str,
|
|
43
|
+
help='specify where cluster log should logged to, it can be multiple paths, "/dev/stdout" is default for '
|
|
44
|
+
"standard output, each worker will have its own log file with process id appended to the path",
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--logging-level",
|
|
48
|
+
"-ll",
|
|
49
|
+
type=str,
|
|
50
|
+
choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
|
|
51
|
+
help="specify the logging level",
|
|
52
|
+
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--logging-config-file",
|
|
55
|
+
type=str,
|
|
56
|
+
default=None,
|
|
57
|
+
help="use standard python the .conf file the specify python logging file configuration format, this will "
|
|
58
|
+
"bypass --logging-paths and --logging-level at the same time, and this will not work on per worker logging",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--object-storage-address",
|
|
62
|
+
"-osa",
|
|
63
|
+
default=None,
|
|
64
|
+
help="specify the object storage server address, e.g.: tcp://localhost:2346",
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument("scheduler_address", nargs="?", type=str, help="scheduler address to connect to")
|
|
67
|
+
return parser.parse_args()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
args = get_args()
|
|
72
|
+
symphony_config = load_config(SymphonyWorkerConfig, args.config, args, section_name="symphony_worker_adapter")
|
|
73
|
+
register_event_loop(symphony_config.event_loop)
|
|
74
|
+
|
|
75
|
+
setup_logger(symphony_config.logging_paths, symphony_config.logging_config_file, symphony_config.logging_level)
|
|
76
|
+
|
|
77
|
+
symphony_worker_adapter = SymphonyWorkerAdapter(
|
|
78
|
+
address=symphony_config.scheduler_address,
|
|
79
|
+
object_storage_address=symphony_config.object_storage_address,
|
|
80
|
+
capabilities=symphony_config.worker_capabilities.capabilities,
|
|
81
|
+
task_queue_size=symphony_config.worker_task_queue_size,
|
|
82
|
+
service_name=symphony_config.service_name,
|
|
83
|
+
base_concurrency=symphony_config.base_concurrency,
|
|
84
|
+
heartbeat_interval_seconds=symphony_config.heartbeat_interval,
|
|
85
|
+
death_timeout_seconds=symphony_config.death_timeout_seconds,
|
|
86
|
+
event_loop=symphony_config.event_loop,
|
|
87
|
+
io_threads=symphony_config.io_threads,
|
|
88
|
+
logging_paths=symphony_config.logging_paths,
|
|
89
|
+
logging_level=symphony_config.logging_level,
|
|
90
|
+
logging_config_file=symphony_config.logging_config_file,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
app = symphony_worker_adapter.create_app()
|
|
94
|
+
web.run_app(app, host=symphony_config.adapter_web_host, port=symphony_config.adapter_web_port)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
scaler/io/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import uuid
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from typing import Awaitable, Callable, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import zmq.asyncio
|
|
8
|
+
from zmq import Frame
|
|
9
|
+
|
|
10
|
+
from scaler.config.types.zmq import ZMQConfig
|
|
11
|
+
from scaler.io.mixins import AsyncBinder
|
|
12
|
+
from scaler.io.utility import deserialize, serialize
|
|
13
|
+
from scaler.protocol.python.mixins import Message
|
|
14
|
+
from scaler.protocol.python.status import BinderStatus
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ZMQAsyncBinder(AsyncBinder):
|
|
18
|
+
def __init__(self, context: zmq.asyncio.Context, name: str, address: ZMQConfig, identity: Optional[bytes] = None):
|
|
19
|
+
self._address = address
|
|
20
|
+
|
|
21
|
+
if identity is None:
|
|
22
|
+
identity = f"{os.getpid()}|{name}|{uuid.uuid4()}".encode()
|
|
23
|
+
self._identity = identity
|
|
24
|
+
|
|
25
|
+
self._context = context
|
|
26
|
+
self._socket = self._context.socket(zmq.ROUTER)
|
|
27
|
+
self.__set_socket_options()
|
|
28
|
+
self._socket.bind(self._address.to_address())
|
|
29
|
+
|
|
30
|
+
self._callback: Optional[Callable[[bytes, Message], Awaitable[None]]] = None
|
|
31
|
+
|
|
32
|
+
self._received: Dict[str, int] = defaultdict(lambda: 0)
|
|
33
|
+
self._sent: Dict[str, int] = defaultdict(lambda: 0)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def identity(self):
|
|
37
|
+
return self._identity
|
|
38
|
+
|
|
39
|
+
def destroy(self):
|
|
40
|
+
self._context.destroy(linger=0)
|
|
41
|
+
|
|
42
|
+
def register(self, callback: Callable[[bytes, Message], Awaitable[None]]):
|
|
43
|
+
self._callback = callback
|
|
44
|
+
|
|
45
|
+
async def routine(self):
|
|
46
|
+
frames: List[Frame] = await self._socket.recv_multipart(copy=False)
|
|
47
|
+
if not self.__is_valid_message(frames):
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
source, payload = frames
|
|
51
|
+
try:
|
|
52
|
+
message: Optional[Message] = deserialize(payload.bytes)
|
|
53
|
+
if message is None:
|
|
54
|
+
logging.error(f"received unknown message from {source.bytes!r}: {payload!r}")
|
|
55
|
+
return
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logging.error(f"{self.__get_prefix()} failed to deserialize message from {source.bytes!r}: {e}")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
self.__count_received(message.__class__.__name__)
|
|
61
|
+
await self._callback(source.bytes, message)
|
|
62
|
+
|
|
63
|
+
async def send(self, to: bytes, message: Message):
|
|
64
|
+
self.__count_sent(message.__class__.__name__)
|
|
65
|
+
await self._socket.send_multipart([to, serialize(message)], copy=False)
|
|
66
|
+
|
|
67
|
+
def get_status(self) -> BinderStatus:
|
|
68
|
+
return BinderStatus.new_msg(received=self._received, sent=self._sent)
|
|
69
|
+
|
|
70
|
+
def __set_socket_options(self):
|
|
71
|
+
self._socket.setsockopt(zmq.IDENTITY, self._identity)
|
|
72
|
+
self._socket.setsockopt(zmq.SNDHWM, 0)
|
|
73
|
+
self._socket.setsockopt(zmq.RCVHWM, 0)
|
|
74
|
+
|
|
75
|
+
def __is_valid_message(self, frames: List[Frame]) -> bool:
|
|
76
|
+
if len(frames) != 2:
|
|
77
|
+
logging.error(f"{self.__get_prefix()} received unexpected frames {frames}")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
def __count_received(self, message_type: str):
|
|
83
|
+
self._received[message_type] += 1
|
|
84
|
+
|
|
85
|
+
def __count_sent(self, message_type: str):
|
|
86
|
+
self._sent[message_type] += 1
|
|
87
|
+
|
|
88
|
+
def __get_prefix(self):
|
|
89
|
+
return f"{self.__class__.__name__}[{self._identity.decode()}]:"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Awaitable, Callable, Literal, Optional
|
|
5
|
+
|
|
6
|
+
import zmq.asyncio
|
|
7
|
+
|
|
8
|
+
from scaler.config.types.zmq import ZMQConfig
|
|
9
|
+
from scaler.io.mixins import AsyncConnector
|
|
10
|
+
from scaler.io.utility import deserialize, serialize
|
|
11
|
+
from scaler.protocol.python.mixins import Message
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ZMQAsyncConnector(AsyncConnector):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
context: zmq.asyncio.Context,
|
|
18
|
+
name: str,
|
|
19
|
+
socket_type: int,
|
|
20
|
+
address: ZMQConfig,
|
|
21
|
+
bind_or_connect: Literal["bind", "connect"],
|
|
22
|
+
callback: Optional[Callable[[Message], Awaitable[None]]],
|
|
23
|
+
identity: Optional[bytes],
|
|
24
|
+
):
|
|
25
|
+
self._address = address
|
|
26
|
+
|
|
27
|
+
self._context = context
|
|
28
|
+
self._socket = self._context.socket(socket_type)
|
|
29
|
+
|
|
30
|
+
if identity is None:
|
|
31
|
+
identity = f"{os.getpid()}|{name}|{uuid.uuid4().bytes.hex()}".encode()
|
|
32
|
+
self._identity = identity
|
|
33
|
+
|
|
34
|
+
# set socket option
|
|
35
|
+
self._socket.setsockopt(zmq.IDENTITY, self._identity)
|
|
36
|
+
self._socket.setsockopt(zmq.SNDHWM, 0)
|
|
37
|
+
self._socket.setsockopt(zmq.RCVHWM, 0)
|
|
38
|
+
|
|
39
|
+
if bind_or_connect == "bind":
|
|
40
|
+
self._socket.bind(self.address)
|
|
41
|
+
elif bind_or_connect == "connect":
|
|
42
|
+
self._socket.connect(self.address)
|
|
43
|
+
else:
|
|
44
|
+
raise TypeError("bind_or_connect has to be 'bind' or 'connect'")
|
|
45
|
+
|
|
46
|
+
self._callback: Optional[Callable[[Message], Awaitable[None]]] = callback
|
|
47
|
+
|
|
48
|
+
def __del__(self):
|
|
49
|
+
self.destroy()
|
|
50
|
+
|
|
51
|
+
def destroy(self):
|
|
52
|
+
if self._socket.closed:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
self._socket.close(linger=1)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def identity(self) -> bytes:
|
|
59
|
+
return self._identity
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def socket(self) -> zmq.asyncio.Socket:
|
|
63
|
+
return self._socket
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def address(self) -> str:
|
|
67
|
+
return self._address.to_address()
|
|
68
|
+
|
|
69
|
+
async def routine(self):
|
|
70
|
+
if self._callback is None:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
message: Optional[Message] = await self.receive()
|
|
74
|
+
if message is None:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
await self._callback(message)
|
|
78
|
+
|
|
79
|
+
async def receive(self) -> Optional[Message]:
|
|
80
|
+
if self._context.closed:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
if self._socket.closed:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
payload = await self._socket.recv(copy=False)
|
|
87
|
+
result: Optional[Message] = deserialize(payload.bytes)
|
|
88
|
+
if result is None:
|
|
89
|
+
logging.error(f"received unknown message: {payload.bytes!r}")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
async def send(self, message: Message):
|
|
95
|
+
await self._socket.send(serialize(message), copy=False)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import struct
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Dict, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from scaler.io.mixins import AsyncObjectStorageConnector
|
|
9
|
+
from scaler.protocol.capnp._python import _object_storage # noqa
|
|
10
|
+
from scaler.protocol.python.object_storage import ObjectRequestHeader, ObjectResponseHeader, to_capnp_object_id
|
|
11
|
+
from scaler.utility.exceptions import ObjectStorageException
|
|
12
|
+
from scaler.utility.identifiers import ObjectID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PyAsyncObjectStorageConnector(AsyncObjectStorageConnector):
|
|
16
|
+
"""An asyncio connector that uses an raw TCP socket to connect to a Scaler's object storage instance."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._host: Optional[str] = None
|
|
20
|
+
self._port: Optional[int] = None
|
|
21
|
+
|
|
22
|
+
self._connected_event = asyncio.Event()
|
|
23
|
+
|
|
24
|
+
self._reader: Optional[asyncio.StreamReader] = None
|
|
25
|
+
self._writer: Optional[asyncio.StreamWriter] = None
|
|
26
|
+
|
|
27
|
+
self._next_request_id = 0
|
|
28
|
+
self._pending_get_requests: Dict[ObjectID, asyncio.Future] = {}
|
|
29
|
+
|
|
30
|
+
self._identity: bytes = (
|
|
31
|
+
f"{self.__class__.__name__}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}".encode()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def __del__(self):
|
|
35
|
+
if not self.is_connected():
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
self._writer.close()
|
|
39
|
+
|
|
40
|
+
async def connect(self, host: str, port: int):
|
|
41
|
+
self._host = host
|
|
42
|
+
self._port = port
|
|
43
|
+
|
|
44
|
+
if self.is_connected():
|
|
45
|
+
raise ObjectStorageException("connector is already connected.")
|
|
46
|
+
|
|
47
|
+
self._reader, self._writer = await asyncio.open_connection(self._host, self._port)
|
|
48
|
+
await self.__read_framed_message()
|
|
49
|
+
self.__write_framed(self._identity)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
await self._writer.drain()
|
|
53
|
+
except ConnectionResetError:
|
|
54
|
+
self.__raise_connection_failure()
|
|
55
|
+
|
|
56
|
+
# Makes sure the socket is TCP_NODELAY. It seems to be the case by default, but that's not specified in the
|
|
57
|
+
# asyncio's documentation and might change in the future.
|
|
58
|
+
self._writer.get_extra_info("socket").setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
59
|
+
|
|
60
|
+
self._connected_event.set()
|
|
61
|
+
|
|
62
|
+
async def wait_until_connected(self):
|
|
63
|
+
await self._connected_event.wait()
|
|
64
|
+
|
|
65
|
+
def is_connected(self) -> bool:
|
|
66
|
+
return self._connected_event.is_set()
|
|
67
|
+
|
|
68
|
+
async def destroy(self):
|
|
69
|
+
if not self.is_connected():
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
if not self._writer.is_closing:
|
|
73
|
+
self._writer.close()
|
|
74
|
+
|
|
75
|
+
await self._writer.wait_closed()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def reader(self) -> Optional[asyncio.StreamReader]:
|
|
79
|
+
return self._reader
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def writer(self) -> Optional[asyncio.StreamWriter]:
|
|
83
|
+
return self._writer
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def address(self) -> str:
|
|
87
|
+
self.__ensure_is_connected()
|
|
88
|
+
return f"tcp://{self._host}:{self._port}"
|
|
89
|
+
|
|
90
|
+
async def routine(self):
|
|
91
|
+
await self.wait_until_connected()
|
|
92
|
+
|
|
93
|
+
response = await self.__receive_response()
|
|
94
|
+
if response is None:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
header, payload = response
|
|
98
|
+
|
|
99
|
+
if header.response_type != ObjectResponseHeader.ObjectResponseType.GetOK:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
pending_get_future = self._pending_get_requests.pop(header.object_id, None)
|
|
103
|
+
|
|
104
|
+
if pending_get_future is None:
|
|
105
|
+
logging.warning(f"unknown get-ok response for unrequested object_id={repr(header.object_id)}.")
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
pending_get_future.set_result(payload)
|
|
109
|
+
|
|
110
|
+
async def set_object(self, object_id: ObjectID, payload: bytes) -> None:
|
|
111
|
+
await self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
|
|
112
|
+
|
|
113
|
+
async def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytes:
|
|
114
|
+
pending_get_future = self._pending_get_requests.get(object_id)
|
|
115
|
+
|
|
116
|
+
if pending_get_future is None:
|
|
117
|
+
pending_get_future = asyncio.Future()
|
|
118
|
+
self._pending_get_requests[object_id] = pending_get_future
|
|
119
|
+
|
|
120
|
+
await self.__send_request(
|
|
121
|
+
object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject, None
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return await pending_get_future
|
|
125
|
+
|
|
126
|
+
async def delete_object(self, object_id: ObjectID) -> None:
|
|
127
|
+
await self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject, None)
|
|
128
|
+
|
|
129
|
+
async def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
|
|
130
|
+
object_id_payload = to_capnp_object_id(object_id).to_bytes()
|
|
131
|
+
|
|
132
|
+
await self.__send_request(
|
|
133
|
+
new_object_id,
|
|
134
|
+
len(object_id_payload),
|
|
135
|
+
ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
|
|
136
|
+
object_id_payload,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def __ensure_is_connected(self):
|
|
140
|
+
if self._writer is None:
|
|
141
|
+
raise ObjectStorageException("connector is not connected.")
|
|
142
|
+
|
|
143
|
+
if self._writer.is_closing():
|
|
144
|
+
raise ObjectStorageException("connection is closed.")
|
|
145
|
+
|
|
146
|
+
async def __send_request(
|
|
147
|
+
self,
|
|
148
|
+
object_id: ObjectID,
|
|
149
|
+
payload_length: int,
|
|
150
|
+
request_type: ObjectRequestHeader.ObjectRequestType,
|
|
151
|
+
payload: Optional[bytes],
|
|
152
|
+
):
|
|
153
|
+
self.__ensure_is_connected()
|
|
154
|
+
assert self._writer is not None
|
|
155
|
+
|
|
156
|
+
request_id = self._next_request_id
|
|
157
|
+
self._next_request_id += 1
|
|
158
|
+
self._next_request_id %= 2**64 - 1 # UINT64_MAX
|
|
159
|
+
|
|
160
|
+
header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
|
|
161
|
+
|
|
162
|
+
self.__write_request_header(header)
|
|
163
|
+
|
|
164
|
+
if payload is not None:
|
|
165
|
+
self.__write_request_payload(payload)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
await self._writer.drain()
|
|
169
|
+
except ConnectionResetError:
|
|
170
|
+
self.__raise_connection_failure()
|
|
171
|
+
|
|
172
|
+
def __write_request_header(self, header: ObjectRequestHeader):
|
|
173
|
+
assert self._writer is not None
|
|
174
|
+
self.__write_framed(header.get_message().to_bytes())
|
|
175
|
+
|
|
176
|
+
def __write_request_payload(self, payload: bytes):
|
|
177
|
+
assert self._writer is not None
|
|
178
|
+
self.__write_framed(payload)
|
|
179
|
+
|
|
180
|
+
async def __receive_response(self) -> Optional[Tuple[ObjectResponseHeader, bytes]]:
|
|
181
|
+
assert self._reader is not None
|
|
182
|
+
|
|
183
|
+
if self._writer.is_closing():
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
header = await self.__read_response_header()
|
|
188
|
+
payload = await self.__read_response_payload(header)
|
|
189
|
+
except asyncio.IncompleteReadError:
|
|
190
|
+
self.__raise_connection_failure()
|
|
191
|
+
|
|
192
|
+
return header, payload
|
|
193
|
+
|
|
194
|
+
async def __read_response_header(self) -> ObjectResponseHeader:
|
|
195
|
+
assert self._reader is not None
|
|
196
|
+
|
|
197
|
+
header_data = await self.__read_framed_message()
|
|
198
|
+
assert len(header_data) == ObjectResponseHeader.MESSAGE_LENGTH
|
|
199
|
+
|
|
200
|
+
with _object_storage.ObjectResponseHeader.from_bytes(header_data) as header_message:
|
|
201
|
+
return ObjectResponseHeader(header_message)
|
|
202
|
+
|
|
203
|
+
async def __read_response_payload(self, header: ObjectResponseHeader) -> bytes:
|
|
204
|
+
assert self._reader is not None
|
|
205
|
+
|
|
206
|
+
if header.payload_length > 0:
|
|
207
|
+
res = await self.__read_framed_message()
|
|
208
|
+
assert len(res) == header.payload_length
|
|
209
|
+
return res
|
|
210
|
+
else:
|
|
211
|
+
return b""
|
|
212
|
+
|
|
213
|
+
async def __read_framed_message(self) -> bytes:
|
|
214
|
+
length_bytes = await self._reader.readexactly(8)
|
|
215
|
+
(payload_length,) = struct.unpack("<Q", length_bytes)
|
|
216
|
+
return await self._reader.readexactly(payload_length) if payload_length > 0 else bytes()
|
|
217
|
+
|
|
218
|
+
def __write_framed(self, payload: bytes):
|
|
219
|
+
self._writer.write(struct.pack("<Q", len(payload)))
|
|
220
|
+
self._writer.write(payload)
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def __raise_connection_failure():
|
|
225
|
+
raise ObjectStorageException("connection failure to object storage server.")
|