bec-ipython-client 4.3.0__tar.gz → 4.3.2__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.
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/PKG-INFO +1 -1
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/pyproject.toml +1 -1
- bec_ipython_client-4.3.2/tests/end-2-end/test_procedures_e2e.py +322 -0
- bec_ipython_client-4.3.0/tests/end-2-end/test_procedures_e2e.py +0 -135
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/.gitignore +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/beamline_mixin.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/bec_magics.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/bec_startup.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/device_progress.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/ipython_live_updates.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/live_table.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/move_device.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/utils.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/gui_client.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/high_level_interfaces/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/high_level_interfaces/bec_hli.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/high_level_interfaces/spec_hli.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/main.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/SLS/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/SLS/sls_info.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/XTreme/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/XTreme/x-treme.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/__init__.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/flomni/flomni_config.yaml +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/prettytable.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/progressbar.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/signals.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/demo.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/conftest.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_beamline_mixins.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_bec_client.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_device_progress.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_gui_client.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_ipython_live_updates.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_live_table.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_move_callback.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_pretty_table.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_signals.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/conftest.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/_ensure_requirements_container.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_acl_e2e.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_actors_e2e.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_scan_workflows_lib_e2e.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_scans_e2e.py +0 -0
- {bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_scans_lib_e2e.py +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
from typing import Callable, Generator
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from pytest_redis import factories as redis_factories
|
|
12
|
+
from redis.exceptions import NoPermissionError
|
|
13
|
+
|
|
14
|
+
from bec_lib import messages
|
|
15
|
+
from bec_lib.endpoints import MessageEndpoints
|
|
16
|
+
from bec_lib.logger import bec_logger
|
|
17
|
+
from bec_lib.procedures.helper import FrontendProcedureHelper, ProcedureState
|
|
18
|
+
from bec_lib.redis_connector import RedisConnector
|
|
19
|
+
from bec_lib.utils.user_acls_test import BECAccessDemo
|
|
20
|
+
from bec_server.procedures.constants import PROCEDURE
|
|
21
|
+
from bec_server.procedures.container_utils import (
|
|
22
|
+
_run_and_capture_error,
|
|
23
|
+
get_backend,
|
|
24
|
+
podman_available,
|
|
25
|
+
)
|
|
26
|
+
from bec_server.procedures.container_worker import ContainerProcedureWorker
|
|
27
|
+
from bec_server.procedures.manager import ProcedureManager
|
|
28
|
+
from bec_server.procedures.subprocess_worker import SubProcessWorker
|
|
29
|
+
|
|
30
|
+
logger = bec_logger.logger
|
|
31
|
+
|
|
32
|
+
# pylint: disable=protected-access
|
|
33
|
+
|
|
34
|
+
# Random order disabled for this module so that the test for building the worker container runs first
|
|
35
|
+
# and we can use lower timeouts for the remaining tests
|
|
36
|
+
pytestmark = pytest.mark.random_order(disabled=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Isolate test managers from the scan server's manager, which subscribes to the same requests.
|
|
40
|
+
proc_redis = redis_factories.redis_proc()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def procedure_manager(proc_redis) -> Generator[ProcedureManager, None, None]:
|
|
45
|
+
"""Provide a manager whose request channels are isolated from running BEC services."""
|
|
46
|
+
manager = ProcedureManager(f"{proc_redis.host}:{proc_redis.port}", SubProcessWorker)
|
|
47
|
+
try:
|
|
48
|
+
yield manager
|
|
49
|
+
finally:
|
|
50
|
+
manager.shutdown()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _wait_while(cond: Callable[[], bool], timeout_s):
|
|
54
|
+
start = time.monotonic()
|
|
55
|
+
while cond():
|
|
56
|
+
if (time.monotonic() - start) > timeout_s:
|
|
57
|
+
raise TimeoutError()
|
|
58
|
+
time.sleep(0.01)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.timeout(100)
|
|
62
|
+
def test_building_worker_image():
|
|
63
|
+
podman_utils = get_backend()
|
|
64
|
+
build = podman_utils.build_worker_image()
|
|
65
|
+
assert len(build._command_output.splitlines()[-1]) == 64 # type: ignore
|
|
66
|
+
assert podman_utils.image_exists(f"bec_procedure_worker:v{version('bec_lib')}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.timeout(30)
|
|
70
|
+
def test_procedure_runner_spawns_worker(procedure_manager):
|
|
71
|
+
manager = procedure_manager
|
|
72
|
+
assert manager.active_workers() == []
|
|
73
|
+
status = manager._helper.request.procedure(
|
|
74
|
+
"sleep", args_kwargs=((), {"time_s": 0.1}), queue="test"
|
|
75
|
+
)
|
|
76
|
+
_wait_while(lambda: not manager.active_workers(), 10)
|
|
77
|
+
status.wait(timeout_s=10, raise_on_failure=True)
|
|
78
|
+
assert status.state == ProcedureState.SUCCESS
|
|
79
|
+
# A completed execution leaves the queue-owned worker available for reuse.
|
|
80
|
+
assert len(manager.active_workers()) == 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.timeout(30)
|
|
84
|
+
def test_happy_path_container_procedure_runner(procedure_manager):
|
|
85
|
+
test_args = (1, 2, 3)
|
|
86
|
+
test_kwargs = {"a": "b", "c": "d"}
|
|
87
|
+
manager = procedure_manager
|
|
88
|
+
status = manager._helper.request.procedure(
|
|
89
|
+
"_log_msg_args", args_kwargs=(test_args, test_kwargs)
|
|
90
|
+
)
|
|
91
|
+
status.wait(timeout_s=10, raise_on_failure=True)
|
|
92
|
+
assert status.state == ProcedureState.SUCCESS
|
|
93
|
+
manager.shutdown()
|
|
94
|
+
assert any("Executing procedure _log_msg_args" in line for line in manager._logs)
|
|
95
|
+
expected = (
|
|
96
|
+
f"Builtin procedure log_message_args_kwargs called with args: {test_args} "
|
|
97
|
+
f"and kwargs: {test_kwargs}"
|
|
98
|
+
)
|
|
99
|
+
assert any(expected in line for line in manager._logs)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.timeout(30)
|
|
103
|
+
def test_personal_procedure_submission_acl(proc_redis):
|
|
104
|
+
"""Only the submitting ACL user can publish or subscribe to their request channel."""
|
|
105
|
+
server = f"{proc_redis.host}:{proc_redis.port}"
|
|
106
|
+
manager = ProcedureManager(server, SubProcessWorker)
|
|
107
|
+
alice = RedisConnector(server)
|
|
108
|
+
bob = RedisConnector(server)
|
|
109
|
+
try:
|
|
110
|
+
access = BECAccessDemo(manager._conn)
|
|
111
|
+
access.add_account("alice", None, "user")
|
|
112
|
+
access.add_account("bob", None, "user")
|
|
113
|
+
alice.authenticate(username="alice", password=None)
|
|
114
|
+
bob.authenticate(username="bob", password=None)
|
|
115
|
+
with (
|
|
116
|
+
patch.object(manager, "_process_queue_request") as process,
|
|
117
|
+
patch.object(manager, "_ack", wraps=manager._ack) as acknowledge,
|
|
118
|
+
):
|
|
119
|
+
helper = FrontendProcedureHelper(alice)
|
|
120
|
+
helper.request.procedure("sleep")
|
|
121
|
+
_wait_while(lambda: process.call_count < 1, 5)
|
|
122
|
+
received = process.call_args.args[0]
|
|
123
|
+
assert received.topic == MessageEndpoints.procedure_request("alice").endpoint
|
|
124
|
+
assert received.value.metadata["client_info"]["acl_user"] == "alice"
|
|
125
|
+
|
|
126
|
+
# A later login must change both the personal channel and the supplied identity.
|
|
127
|
+
alice.authenticate(username="bob", password=None)
|
|
128
|
+
helper.request.procedure("sleep")
|
|
129
|
+
_wait_while(lambda: process.call_count < 2, 5)
|
|
130
|
+
assert process.call_args.args[0].value.metadata["client_info"]["acl_user"] == "bob"
|
|
131
|
+
|
|
132
|
+
forged = messages.ProcedureRequestMessage(
|
|
133
|
+
identifier="sleep", metadata={"client_info": {"acl_user": "alice"}}
|
|
134
|
+
)
|
|
135
|
+
bob.send(MessageEndpoints.procedure_request("bob"), forged)
|
|
136
|
+
_wait_while(lambda: not acknowledge.called, 5)
|
|
137
|
+
acknowledge.assert_called_once_with(
|
|
138
|
+
False, "Username in topic does not match client info.", forged.execution_id
|
|
139
|
+
)
|
|
140
|
+
assert process.call_count == 2
|
|
141
|
+
with pytest.raises(NoPermissionError):
|
|
142
|
+
bob.send(MessageEndpoints.procedure_request("alice"), forged)
|
|
143
|
+
with bob._managed_connection._redis_conn.pubsub() as subscription:
|
|
144
|
+
subscription.subscribe(MessageEndpoints.procedure_request("alice").endpoint)
|
|
145
|
+
with pytest.raises(NoPermissionError):
|
|
146
|
+
subscription.get_message(timeout=1)
|
|
147
|
+
finally:
|
|
148
|
+
alice.shutdown()
|
|
149
|
+
bob.shutdown()
|
|
150
|
+
manager.shutdown()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.timeout(120)
|
|
154
|
+
@pytest.mark.parametrize(
|
|
155
|
+
"worker_type", [ContainerProcedureWorker, SubProcessWorker], ids=["podman", "subprocess"]
|
|
156
|
+
)
|
|
157
|
+
def test_procedure_execution(proc_redis, monkeypatch, worker_type):
|
|
158
|
+
"""Execute a real procedure with either worker backend against isolated Redis.
|
|
159
|
+
|
|
160
|
+
Podman requires the worker image and a host-networked ``local_bec`` pod. For a remote
|
|
161
|
+
Podman VM, BEC_TEST_CONTAINER_REDIS_HOST can supply a container-reachable host.
|
|
162
|
+
"""
|
|
163
|
+
uses_container = worker_type is ContainerProcedureWorker
|
|
164
|
+
if uses_container and not podman_available():
|
|
165
|
+
pytest.skip("Podman is not available")
|
|
166
|
+
host, port = proc_redis.host, proc_redis.port
|
|
167
|
+
queue = f"procedure_test_{uuid4().hex}"
|
|
168
|
+
if uses_container and (container_host := os.environ.get("BEC_TEST_CONTAINER_REDIS_HOST")):
|
|
169
|
+
worker_environment = ContainerProcedureWorker._worker_environment
|
|
170
|
+
monkeypatch.setattr(
|
|
171
|
+
ContainerProcedureWorker,
|
|
172
|
+
"_worker_environment",
|
|
173
|
+
lambda worker: worker_environment(worker)
|
|
174
|
+
| {"redis_server": f"{container_host}:{port}"},
|
|
175
|
+
)
|
|
176
|
+
manager = ProcedureManager(f"{host}:{port}", worker_type)
|
|
177
|
+
try:
|
|
178
|
+
status = manager._helper.request.procedure(
|
|
179
|
+
"_log_msg_args", args_kwargs=(("procedure regression",), {}), queue=queue
|
|
180
|
+
)
|
|
181
|
+
status.wait(timeout_s=90)
|
|
182
|
+
if not status.done or status.state != ProcedureState.SUCCESS:
|
|
183
|
+
logs = manager._conn.xrange(MessageEndpoints.procedure_logs(queue), "-", "+") or []
|
|
184
|
+
output = "".join(entry["data"].data for entry in logs)
|
|
185
|
+
pytest.fail(
|
|
186
|
+
f"Procedure state: {status.state.value}; error: {status.error}\n"
|
|
187
|
+
f"Worker output:\n{output}"
|
|
188
|
+
)
|
|
189
|
+
_wait_while(lambda: bool(manager.active_workers()), PROCEDURE.WORKER.QUEUE_TIMEOUT_S + 10)
|
|
190
|
+
logs = manager._conn.xrange(MessageEndpoints.procedure_logs(queue), "-", "+") or []
|
|
191
|
+
assert any("procedure regression" in entry["data"].data for entry in logs)
|
|
192
|
+
assert any("procedure regression" in line for line in manager._logs)
|
|
193
|
+
finally:
|
|
194
|
+
try:
|
|
195
|
+
manager.shutdown()
|
|
196
|
+
finally:
|
|
197
|
+
if uses_container:
|
|
198
|
+
container_name = f"bec_procedure_{PROCEDURE.BEC_VERSION}_{queue}"
|
|
199
|
+
_run_and_capture_error("podman", "rm", "--force", "--ignore", container_name)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.timeout(120)
|
|
203
|
+
def test_queue_pool_credentials_and_reuse(proc_redis):
|
|
204
|
+
"""Use only the submitted identity in clients, with service control confined to the parent."""
|
|
205
|
+
from bec_lib.script_executor import upload_script
|
|
206
|
+
|
|
207
|
+
server = f"{proc_redis.host}:{proc_redis.port}"
|
|
208
|
+
admin = RedisConnector(server)
|
|
209
|
+
alice = RedisConnector(server)
|
|
210
|
+
manager = None
|
|
211
|
+
try:
|
|
212
|
+
access = BECAccessDemo(admin)
|
|
213
|
+
access.add_account("procedure_service", "service-secret", "admin")
|
|
214
|
+
access.add_account("procedure_alice", None, "user")
|
|
215
|
+
admin._managed_connection._redis_conn.acl_setuser(
|
|
216
|
+
"procedure_alice", enabled=True, reset_passwords=True, passwords=["+first-token"]
|
|
217
|
+
)
|
|
218
|
+
admin.authenticate(username="procedure_service", password="service-secret")
|
|
219
|
+
admin._managed_connection._redis_conn.acl_setuser(
|
|
220
|
+
"procedure_alice", enabled=True, passwords=["+second-token"]
|
|
221
|
+
)
|
|
222
|
+
admin._managed_connection._redis_conn.acl_setuser("default", enabled=False)
|
|
223
|
+
alice.authenticate(username="procedure_alice", password="first-token")
|
|
224
|
+
manager = ProcedureManager(
|
|
225
|
+
server,
|
|
226
|
+
SubProcessWorker,
|
|
227
|
+
redis_credentials={"username": "procedure_service", "token": "service-secret"},
|
|
228
|
+
)
|
|
229
|
+
helper = FrontendProcedureHelper(alice)
|
|
230
|
+
|
|
231
|
+
def execute(queue):
|
|
232
|
+
script_id = upload_script(
|
|
233
|
+
alice,
|
|
234
|
+
"import os, sys\n"
|
|
235
|
+
"assert 'credentials_file' not in os.environ\n"
|
|
236
|
+
"assert self.connector.username == 'procedure_alice'\n"
|
|
237
|
+
"try:\n"
|
|
238
|
+
" self.connector._managed_connection._redis_conn.acl_list()\n"
|
|
239
|
+
"except Exception:\n"
|
|
240
|
+
" pass\n"
|
|
241
|
+
"else:\n"
|
|
242
|
+
" raise AssertionError('Procedure obtained service privileges')\n"
|
|
243
|
+
"print('stdout marker', end='', flush=True)\n"
|
|
244
|
+
"sys.stderr.write('stderr marker')\n"
|
|
245
|
+
"sys.stderr.flush()\n",
|
|
246
|
+
)
|
|
247
|
+
status = helper.request.procedure(
|
|
248
|
+
"run_script", args_kwargs=((script_id,), {}), queue=queue
|
|
249
|
+
)
|
|
250
|
+
status.wait(timeout_s=30, raise_on_failure=True)
|
|
251
|
+
assert status.state == ProcedureState.SUCCESS
|
|
252
|
+
_wait_while(lambda: manager._queues[queue].current is not None, 5)
|
|
253
|
+
return status
|
|
254
|
+
|
|
255
|
+
execute("credential_queue")
|
|
256
|
+
(first_id,) = manager._queues["credential_queue"].workers
|
|
257
|
+
first_pid = manager._queues["credential_queue"].workers[first_id].worker._process.pid
|
|
258
|
+
execute("credential_queue")
|
|
259
|
+
assert list(manager._queues["credential_queue"].workers) == [first_id]
|
|
260
|
+
alice.authenticate(username="procedure_alice", password="second-token")
|
|
261
|
+
execute("credential_queue")
|
|
262
|
+
assert len(manager._queues["credential_queue"].workers) == 2
|
|
263
|
+
(second_id,) = set(manager._queues["credential_queue"].workers) - {first_id}
|
|
264
|
+
assert (
|
|
265
|
+
manager._queues["credential_queue"].workers[second_id].worker._process.pid != first_pid
|
|
266
|
+
)
|
|
267
|
+
alice.authenticate(username="procedure_alice", password="first-token")
|
|
268
|
+
execute("credential_queue")
|
|
269
|
+
assert len(manager._queues["credential_queue"].workers) == 2
|
|
270
|
+
assert (
|
|
271
|
+
manager._queues["credential_queue"].workers[first_id].worker._process.pid == first_pid
|
|
272
|
+
)
|
|
273
|
+
execute("different_queue")
|
|
274
|
+
assert not set(manager._queues["different_queue"].workers) & set(
|
|
275
|
+
manager._queues["credential_queue"].workers
|
|
276
|
+
)
|
|
277
|
+
logs = (
|
|
278
|
+
manager._conn.xrange(MessageEndpoints.procedure_logs("credential_queue"), "-", "+")
|
|
279
|
+
or []
|
|
280
|
+
)
|
|
281
|
+
text = "".join(entry["data"].data for entry in logs)
|
|
282
|
+
assert "stdout marker" in text and "stderr marker" in text
|
|
283
|
+
assert all(
|
|
284
|
+
secret not in text for secret in ("first-token", "second-token", "service-secret")
|
|
285
|
+
)
|
|
286
|
+
finally:
|
|
287
|
+
if manager is not None:
|
|
288
|
+
manager.shutdown()
|
|
289
|
+
BECAccessDemo(admin).set_default_limited(False)
|
|
290
|
+
alice.shutdown()
|
|
291
|
+
admin.shutdown()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@pytest.mark.timeout(60)
|
|
295
|
+
def test_invalid_procedure_token_cannot_fall_back_to_default(proc_redis):
|
|
296
|
+
"""Invalid forwarded credentials must fail even when default has full privileges."""
|
|
297
|
+
server = f"{proc_redis.host}:{proc_redis.port}"
|
|
298
|
+
manager = ProcedureManager(server, SubProcessWorker)
|
|
299
|
+
try:
|
|
300
|
+
BECAccessDemo(manager._conn).add_account("invalid_token_alice", None, "user")
|
|
301
|
+
manager._conn._managed_connection._redis_conn.acl_setuser(
|
|
302
|
+
"invalid_token_alice", enabled=True, reset_passwords=True, passwords=["+correct"]
|
|
303
|
+
)
|
|
304
|
+
request = messages.ProcedureRequestMessage(
|
|
305
|
+
identifier="_log_msg_args",
|
|
306
|
+
args_kwargs=(("must not execute",), {}),
|
|
307
|
+
queue="invalid_credentials",
|
|
308
|
+
metadata={
|
|
309
|
+
"client_info": {"acl_user": "invalid_token_alice", "acl_token": "incorrect-token"}
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
manager._conn.send(MessageEndpoints.procedure_request("invalid_token_alice"), request)
|
|
313
|
+
_wait_while(lambda: not manager._helper.get.unhandled_queue("invalid_credentials"), 30)
|
|
314
|
+
assert (
|
|
315
|
+
manager._helper.get.unhandled_queue("invalid_credentials")[0].execution_id
|
|
316
|
+
== request.execution_id
|
|
317
|
+
)
|
|
318
|
+
output = "\n".join(manager._logs)
|
|
319
|
+
assert "must not execute" not in output
|
|
320
|
+
assert "incorrect-token" not in output
|
|
321
|
+
finally:
|
|
322
|
+
manager.shutdown()
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from importlib.metadata import version
|
|
6
|
-
from typing import TYPE_CHECKING, Callable, Generator
|
|
7
|
-
from unittest.mock import patch
|
|
8
|
-
|
|
9
|
-
import pytest
|
|
10
|
-
|
|
11
|
-
from bec_ipython_client.main import BECIPythonClient
|
|
12
|
-
from bec_lib import messages
|
|
13
|
-
from bec_lib.endpoints import MessageEndpoints
|
|
14
|
-
from bec_lib.logger import bec_logger
|
|
15
|
-
from bec_server.procedures.constants import _CONTAINER, _WORKER
|
|
16
|
-
from bec_server.procedures.container_utils import get_backend
|
|
17
|
-
from bec_server.procedures.manager import ProcedureManager
|
|
18
|
-
from bec_server.procedures.subprocess_worker import SubProcessWorker
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from pytest_bec_e2e.plugin import LogTestTool
|
|
22
|
-
|
|
23
|
-
logger = bec_logger.logger
|
|
24
|
-
|
|
25
|
-
# pylint: disable=protected-access
|
|
26
|
-
|
|
27
|
-
# Random order disabled for this module so that the test for building the worker container runs first
|
|
28
|
-
# and we can use lower timeouts for the remaining tests
|
|
29
|
-
pytestmark = pytest.mark.random_order(disabled=True)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass(frozen=True)
|
|
33
|
-
class PATCHED_CONSTANTS:
|
|
34
|
-
WORKER = _WORKER()
|
|
35
|
-
CONTAINER = _CONTAINER()
|
|
36
|
-
MANAGER_SHUTDOWN_TIMEOUT_S = 2
|
|
37
|
-
BEC_VERSION = version("bec_lib")
|
|
38
|
-
REDIS_HOST = "localhost"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@pytest.fixture
|
|
42
|
-
def client_logtool_and_manager(
|
|
43
|
-
bec_ipython_client_fixture_with_logtool: tuple[BECIPythonClient, "LogTestTool"],
|
|
44
|
-
) -> Generator[tuple[BECIPythonClient, "LogTestTool", ProcedureManager], None, None]:
|
|
45
|
-
client, logtool = bec_ipython_client_fixture_with_logtool
|
|
46
|
-
manager = ProcedureManager(f"{client.connector.host}:{client.connector.port}", SubProcessWorker)
|
|
47
|
-
try:
|
|
48
|
-
yield client, logtool, manager
|
|
49
|
-
finally:
|
|
50
|
-
manager.shutdown()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _wait_while(cond: Callable[[], bool], timeout_s):
|
|
54
|
-
start = time.monotonic()
|
|
55
|
-
while cond():
|
|
56
|
-
if (time.monotonic() - start) > timeout_s:
|
|
57
|
-
raise TimeoutError()
|
|
58
|
-
time.sleep(0.01)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@pytest.mark.timeout(100)
|
|
62
|
-
def test_building_worker_image():
|
|
63
|
-
podman_utils = get_backend()
|
|
64
|
-
build = podman_utils.build_worker_image()
|
|
65
|
-
assert len(build._command_output.splitlines()[-1]) == 64 # type: ignore
|
|
66
|
-
assert podman_utils.image_exists(f"bec_procedure_worker:v{version('bec_lib')}")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@pytest.mark.timeout(100)
|
|
70
|
-
@patch("bec_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
|
|
71
|
-
@patch("bec_server.procedures.oop_worker_base.PROCEDURE", PATCHED_CONSTANTS())
|
|
72
|
-
@patch("bec_server.procedures.container_worker.PROCEDURE", PATCHED_CONSTANTS())
|
|
73
|
-
def test_procedure_runner_spawns_worker(
|
|
74
|
-
client_logtool_and_manager: tuple[BECIPythonClient, "LogTestTool", ProcedureManager],
|
|
75
|
-
):
|
|
76
|
-
client, _, manager = client_logtool_and_manager
|
|
77
|
-
assert manager._active_workers == {}
|
|
78
|
-
endpoint = MessageEndpoints.procedure_request()
|
|
79
|
-
msg = messages.ProcedureRequestMessage(
|
|
80
|
-
identifier="sleep", args_kwargs=((), {"time_s": 0.1}), queue="test"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
logs = []
|
|
84
|
-
|
|
85
|
-
def cb(worker: SubProcessWorker):
|
|
86
|
-
nonlocal logs
|
|
87
|
-
logs = worker.logs()
|
|
88
|
-
|
|
89
|
-
manager.add_callback("test", cb)
|
|
90
|
-
client.connector.send(endpoint, msg)
|
|
91
|
-
|
|
92
|
-
_wait_while(lambda: manager._active_workers == {}, 5)
|
|
93
|
-
_wait_while(lambda: manager._active_workers != {}, 90)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@pytest.mark.timeout(100)
|
|
97
|
-
@patch("bec_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
|
|
98
|
-
@patch("bec_server.procedures.oop_worker_base.PROCEDURE", PATCHED_CONSTANTS())
|
|
99
|
-
@patch("bec_server.procedures.container_worker.PROCEDURE", PATCHED_CONSTANTS())
|
|
100
|
-
def test_happy_path_container_procedure_runner(
|
|
101
|
-
client_logtool_and_manager: tuple[BECIPythonClient, "LogTestTool", ProcedureManager],
|
|
102
|
-
):
|
|
103
|
-
test_args = (1, 2, 3)
|
|
104
|
-
test_kwargs = {"a": "b", "c": "d"}
|
|
105
|
-
client, logtool, manager = client_logtool_and_manager
|
|
106
|
-
assert manager._active_workers == {}
|
|
107
|
-
conn = client.connector
|
|
108
|
-
endpoint = MessageEndpoints.procedure_request()
|
|
109
|
-
msg = messages.ProcedureRequestMessage(
|
|
110
|
-
identifier="_log_msg_args", args_kwargs=(test_args, test_kwargs)
|
|
111
|
-
)
|
|
112
|
-
conn.send(endpoint, msg)
|
|
113
|
-
|
|
114
|
-
_wait_while(lambda: manager._active_workers == {}, 5)
|
|
115
|
-
_wait_while(lambda: manager._active_workers != {}, 90)
|
|
116
|
-
|
|
117
|
-
logtool.fetch()
|
|
118
|
-
assert logtool.is_present_in_any_message("procedure accepted: True, message:")
|
|
119
|
-
assert logtool.is_present_in_any_message(
|
|
120
|
-
"Procedure worker started container for queue primary"
|
|
121
|
-
), f"Log content relating to procedures: {manager._logs}"
|
|
122
|
-
|
|
123
|
-
res, msg = logtool.are_present_in_order(
|
|
124
|
-
[
|
|
125
|
-
"Procedure worker 'primary' status update: IDLE",
|
|
126
|
-
"Procedure worker 'primary' status update: RUNNING",
|
|
127
|
-
"Procedure worker 'primary' status update: IDLE",
|
|
128
|
-
"Procedure worker 'primary' status update: FINISHED",
|
|
129
|
-
]
|
|
130
|
-
)
|
|
131
|
-
assert res, f"failed on {msg}"
|
|
132
|
-
|
|
133
|
-
assert logtool.is_present_in_any_message(
|
|
134
|
-
f"Builtin procedure log_message_args_kwargs called with args: {test_args} and kwargs: {test_kwargs}"
|
|
135
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/live_table.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/callbacks/move_device.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/SLS/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/SLS/sls_info.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/XTreme/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/XTreme/x-treme.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/bec_ipython_client/plugins/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_beamline_mixins.py
RENAMED
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_device_progress.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_move_callback.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/client_tests/test_pretty_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.3.0 → bec_ipython_client-4.3.2}/tests/end-2-end/test_scan_workflows_lib_e2e.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|