Skip to content

Commit ef9388d

Browse files
committed
Migrate node removal and port allow runners onto the task runner
Both are mechanical translations of their suspend points into the handler vocabulary, and neither had a retry counter to hand over — every gate in them deferred. Node removal's "incomplete, retry later" pass becomes TaskProgress. It is progress, not failure: the removal waits on device failure-migration that can take hours, and treating each poll as a retry would have earned it an exponential backoff it never had. Port allow keeps its documented opt-out — no IN_ACTIVATION eligibility gate, because activation needs exactly these ports open. Two of its paths returned bare, leaving the task untouched for the loop to revisit; under the driver a bare return means success, so both are now explicit defers. Its abort-on-half-open-hublvol path keeps the abort side effect in the handler and raises TaskAbort for the outcome. Adds an import-smoke over all thirteen migrated runners that also asserts no two of them claim the same task function name — the lease guards against a second host, not against a second spec.
1 parent dcb58d3 commit ef9388d

5 files changed

Lines changed: 163 additions & 280 deletions

File tree

Lines changed: 23 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# coding=utf-8
2-
import time
3-
4-
52
from simplyblock_core import db_controller, storage_node_ops, utils, constants
6-
from simplyblock_core.controllers import tasks_controller
73
from simplyblock_core.models.job_schedule import JobSchedule
84
from simplyblock_core.models.cluster import Cluster
5+
from simplyblock_core.services.task_runner_base import (
6+
RunnerSpec,
7+
TaskProgress,
8+
serve,
9+
)
910

1011

1112
logger = utils.get_logger(__name__)
@@ -19,76 +20,29 @@ def process_task(task):
1920
2021
node_removal_orchestrate is idempotent and resumable: it returns True only
2122
when the node is fully REMOVED, and False to mean "incomplete, retry later"
22-
(most commonly: device failure-migration still in progress). On False we
23-
suspend the task so the outer loop revisits it on the next tick instead of
24-
busy-spinning here for what can be hours.
23+
(most commonly: device failure-migration still in progress, which can take
24+
hours). Incomplete is progress, not failure — it consumes no retry, and the
25+
task stays RUNNING so the next tick picks it straight back up.
2526
"""
26-
if task.canceled:
27-
task.function_result = "canceled"
28-
task.status = JobSchedule.STATUS_DONE
29-
task.write_to_db(db.kv_store)
30-
return True
31-
32-
cluster = db.get_cluster_by_id(task.cluster_id)
33-
if cluster.status == Cluster.STATUS_IN_ACTIVATION:
34-
task.function_result = "cluster is in_activation, waiting"
35-
task.status = JobSchedule.STATUS_SUSPENDED
36-
task.write_to_db(db.kv_store)
37-
return False
27+
force_remove = bool(task.function_params.get("force_remove", False))
28+
if not storage_node_ops.node_removal_orchestrate(task.node_id, force_remove=force_remove):
29+
raise TaskProgress("removal in progress, retrying")
3830

39-
if task.status != JobSchedule.STATUS_RUNNING:
40-
task.status = JobSchedule.STATUS_RUNNING
41-
task.write_to_db(db.kv_store)
31+
task.function_result = "Node removed"
4232

43-
force_remove = bool(task.function_params.get("force_remove", False))
44-
try:
45-
done = storage_node_ops.node_removal_orchestrate(task.node_id, force_remove=force_remove)
46-
except Exception as e:
47-
logger.error(f"Node-removal task {task.uuid} raised: {e}")
48-
logger.exception(e)
49-
task.function_result = f"error: {e}"
50-
task.retry += 1
51-
task.status = JobSchedule.STATUS_SUSPENDED
52-
task.write_to_db(db.kv_store)
53-
return False
5433

55-
if done:
56-
task.function_result = "Node removed"
57-
task.status = JobSchedule.STATUS_DONE
58-
task.write_to_db(db.kv_store)
59-
return True
34+
SPEC = RunnerSpec(
35+
name="tasks-runner-node-removal",
36+
function_names=[JobSchedule.FN_NODE_REMOVAL],
37+
handler=process_task,
38+
is_eligible=lambda task, cluster: cluster.status != Cluster.STATUS_IN_ACTIVATION,
39+
interval=constants.TASK_EXEC_INTERVAL_SEC,
40+
)
6041

61-
# Incomplete: a phase asked us to retry (typically waiting on migration).
62-
task.function_result = "removal in progress, retrying"
63-
task.retry += 1
64-
task.status = JobSchedule.STATUS_SUSPENDED
65-
task.write_to_db(db.kv_store)
66-
return False
6742

43+
def main():
44+
serve(SPEC)
6845

69-
logger.info("Starting Tasks runner node removal...")
7046

71-
while True:
72-
time.sleep(constants.TASK_EXEC_INTERVAL_SEC)
73-
clusters = db.get_clusters()
74-
if not clusters:
75-
logger.error("No clusters found!")
76-
continue
77-
for cl in clusters:
78-
tasks = db.get_job_tasks(cl.get_id(), reverse=False)
79-
for task in tasks:
80-
if task.function_name != JobSchedule.FN_NODE_REMOVAL:
81-
continue
82-
if task.status == JobSchedule.STATUS_DONE:
83-
continue
84-
# get a fresh object: cancel/other writers may have changed it
85-
task = db.get_task_by_id(task.uuid)
86-
# Lease gate: skip a task another live runner host owns.
87-
if not tasks_controller.claim_task(task):
88-
logger.info(f"Node-removal task {task.uuid} owned by another runner host; skipping")
89-
continue
90-
try:
91-
process_task(task)
92-
except Exception as e:
93-
logger.error(f"Node-removal task {task.uuid} processing crashed: {e}")
94-
logger.exception(e)
47+
if __name__ == "__main__":
48+
main()

0 commit comments

Comments
 (0)