Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f94388f49 |
@@ -2,7 +2,7 @@ name: archive-control-archive
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
archive-client:
|
archive-client:
|
||||||
image: sodium/archive-clients:v0.1.8
|
image: sodium/archive-clients:v0.1.9
|
||||||
user: "1000:1000"
|
user: "1000:1000"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["--config", "/etc/archive-control/client.toml"]
|
command: ["--config", "/etc/archive-control/client.toml"]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: archive-control-cache
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
archive-client:
|
archive-client:
|
||||||
image: sodium/archive-clients:v0.1.8
|
image: sodium/archive-clients:v0.1.9
|
||||||
user: "1001:1001"
|
user: "1001:1001"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: archive-control-cache
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
archive-client:
|
archive-client:
|
||||||
image: sodium/archive-clients:v0.1.8
|
image: sodium/archive-clients:v0.1.9
|
||||||
user: "1001:1001"
|
user: "1001:1001"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ cache/archive routes according to policy.
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
archive-client:
|
archive-client:
|
||||||
image: sodium/archive-clients:v0.1.8
|
image: sodium/archive-clients:v0.1.9
|
||||||
user: "1001:1001"
|
user: "1001:1001"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["archive-client", "--config", "/etc/archive-control/client.toml"]
|
command: ["archive-client", "--config", "/etc/archive-control/client.toml"]
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "archive-clients"
|
name = "archive-clients"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = ["protobuf==7.35.1", "websockets==16.0"]
|
dependencies = ["protobuf==7.35.1", "websockets==16.0"]
|
||||||
|
|
||||||
|
|||||||
+42
-24
@@ -120,7 +120,11 @@ class ClientJobExecutor:
|
|||||||
replay = self._replay(
|
replay = self._replay(
|
||||||
command.job_id, command.expected_last_event_sequence
|
command.job_id, command.expected_last_event_sequence
|
||||||
)
|
)
|
||||||
if replay and replay[-1].type in {
|
step_replay = [
|
||||||
|
event for event in replay
|
||||||
|
if event.progress.step == command.step
|
||||||
|
]
|
||||||
|
if step_replay and step_replay[-1].type in {
|
||||||
control_pb2.JOB_EVENT_TYPE_STEP_SUCCEEDED,
|
control_pb2.JOB_EVENT_TYPE_STEP_SUCCEEDED,
|
||||||
control_pb2.JOB_EVENT_TYPE_COMMITTED,
|
control_pb2.JOB_EVENT_TYPE_COMMITTED,
|
||||||
control_pb2.JOB_EVENT_TYPE_SUCCEEDED,
|
control_pb2.JOB_EVENT_TYPE_SUCCEEDED,
|
||||||
@@ -132,31 +136,45 @@ class ClientJobExecutor:
|
|||||||
for event in replay:
|
for event in replay:
|
||||||
event_callback(event)
|
event_callback(event)
|
||||||
return replay
|
return replay
|
||||||
started = replay[0] if replay else self._event(
|
if step_replay:
|
||||||
definition,
|
started = step_replay[0]
|
||||||
sequence=command.expected_last_event_sequence + 1,
|
cursor = step_replay[-1]
|
||||||
revision=command.expected_job_revision + 1,
|
emitted = list(step_replay)
|
||||||
event_type=control_pb2.JOB_EVENT_TYPE_STEP_STARTED,
|
else:
|
||||||
state=job_pb2.JOB_STATE_RUNNING,
|
previous = replay[-1] if replay else None
|
||||||
committed=(
|
started = self._event(
|
||||||
self._committed(command.job_id)
|
definition,
|
||||||
or command.step == job_pb2.JOB_STEP_KIND_STAGING_CLEANUP
|
sequence=(
|
||||||
or (
|
previous.sequence + 1
|
||||||
command.step == job_pb2.JOB_STEP_KIND_SAFE_FILE_UNLINK
|
if previous is not None
|
||||||
and self.store.get_job_artifact(
|
else command.expected_last_event_sequence + 1
|
||||||
command.job_id, "qb-entry-removed"
|
),
|
||||||
) is not None
|
revision=(
|
||||||
)
|
previous.job_revision + 1
|
||||||
),
|
if previous is not None
|
||||||
step=command.step,
|
else command.expected_job_revision + 1
|
||||||
step_state=job_pb2.STEP_STATE_RUNNING,
|
),
|
||||||
)
|
event_type=control_pb2.JOB_EVENT_TYPE_STEP_STARTED,
|
||||||
if not replay:
|
state=job_pb2.JOB_STATE_RUNNING,
|
||||||
|
committed=(
|
||||||
|
self._committed(command.job_id)
|
||||||
|
or command.step == job_pb2.JOB_STEP_KIND_STAGING_CLEANUP
|
||||||
|
or (
|
||||||
|
command.step == job_pb2.JOB_STEP_KIND_SAFE_FILE_UNLINK
|
||||||
|
and self.store.get_job_artifact(
|
||||||
|
command.job_id, "qb-entry-removed"
|
||||||
|
) is not None
|
||||||
|
)
|
||||||
|
),
|
||||||
|
step=command.step,
|
||||||
|
step_state=job_pb2.STEP_STATE_RUNNING,
|
||||||
|
)
|
||||||
self._record(definition, started)
|
self._record(definition, started)
|
||||||
|
cursor = started
|
||||||
|
emitted = [started]
|
||||||
if event_callback is not None:
|
if event_callback is not None:
|
||||||
event_callback(started)
|
for event in emitted:
|
||||||
emitted = [started]
|
event_callback(event)
|
||||||
cursor = started
|
|
||||||
speed_sample = [time.monotonic(), 0]
|
speed_sample = [time.monotonic(), 0]
|
||||||
|
|
||||||
def progress(
|
def progress(
|
||||||
|
|||||||
@@ -111,6 +111,72 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
|||||||
syncthing=SlowRescanSyncthing(),
|
syncthing=SlowRescanSyncthing(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_target_step_advances_past_replayed_source_completion(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
store = ClientStore(root / "client.db")
|
||||||
|
store.initialize()
|
||||||
|
definition = job_pb2.JobDefinition(
|
||||||
|
job_id=str(uuid4()),
|
||||||
|
idempotency_key=str(uuid4()),
|
||||||
|
operation=job_pb2.JOB_OPERATION_ARCHIVE,
|
||||||
|
resource_display_name="fixture",
|
||||||
|
transfer={
|
||||||
|
"source_client_id": "cache-1",
|
||||||
|
"target_client_id": "archive-1",
|
||||||
|
"route_id": "route-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
definition.resource_id.info_hash_v1_hex = "a" * 40
|
||||||
|
definition.created_at.GetCurrentTime()
|
||||||
|
executor = ClientJobExecutor(
|
||||||
|
client_id="archive-1",
|
||||||
|
qbittorrent=Mock(),
|
||||||
|
store=store,
|
||||||
|
qb_root=root,
|
||||||
|
qb_api_root=Path("/downloads"),
|
||||||
|
route_path=lambda _: root,
|
||||||
|
syncthing_transport=Mock(),
|
||||||
|
sparse_supported=True,
|
||||||
|
poll_interval=0,
|
||||||
|
)
|
||||||
|
executor.assign(control_pb2.AssignJobCommand(
|
||||||
|
job=definition,
|
||||||
|
expected_job_revision=1,
|
||||||
|
expected_last_event_sequence=1,
|
||||||
|
))
|
||||||
|
source_complete = executor._event(
|
||||||
|
definition,
|
||||||
|
sequence=5,
|
||||||
|
revision=4,
|
||||||
|
event_type=control_pb2.JOB_EVENT_TYPE_STEP_SUCCEEDED,
|
||||||
|
state=job_pb2.JOB_STATE_RUNNING,
|
||||||
|
committed=False,
|
||||||
|
step=job_pb2.JOB_STEP_KIND_SOURCE_STAGE,
|
||||||
|
step_state=job_pb2.STEP_STATE_SUCCEEDED,
|
||||||
|
)
|
||||||
|
executor._record(definition, source_complete)
|
||||||
|
|
||||||
|
with patch.object(executor, "_wait_for_syncthing"):
|
||||||
|
events = executor.execute(control_pb2.ExecuteStepCommand(
|
||||||
|
job_id=definition.job_id,
|
||||||
|
expected_job_revision=4,
|
||||||
|
expected_last_event_sequence=5,
|
||||||
|
step=job_pb2.JOB_STEP_KIND_SYNCTHING_TRANSFER,
|
||||||
|
attempt=1,
|
||||||
|
))
|
||||||
|
|
||||||
|
self.assertEqual(events[0].sequence, 6)
|
||||||
|
self.assertEqual(
|
||||||
|
events[0].progress.step,
|
||||||
|
job_pb2.JOB_STEP_KIND_SYNCTHING_TRANSFER,
|
||||||
|
)
|
||||||
|
self.assertEqual(events[-1].sequence, 7)
|
||||||
|
self.assertEqual(
|
||||||
|
events[-1].type,
|
||||||
|
control_pb2.JOB_EVENT_TYPE_STEP_SUCCEEDED,
|
||||||
|
)
|
||||||
|
|
||||||
def _run_transfer(
|
def _run_transfer(
|
||||||
self,
|
self,
|
||||||
root: Path,
|
root: Path,
|
||||||
|
|||||||
Reference in New Issue
Block a user