refactor: replace yarn CLI shell-out with YARN REST API
yarn_client.py no longer invokes the 'yarn' binary via subprocess; it uses
httpx against /ws/v1/cluster/apps/* endpoints. This means the runtime image
no longer needs the Hadoop client installation — the only YARN-side
dependency left in the container is the config dir consumed by
spark-submit itself.
New model field:
- Job.yarn_rm_url: str | None
- PendingSubmission.yarn_rm_url: str | None (snapshotted at prepare)
prepare_submit_job snapshots Connection.yarn_rm_url into the pending
record (consistent with the existing master/deploy_mode/spark_conf
snapshot pattern); confirm_submit_job copies it onto the Job so
status/logs/kill can use it without re-looking-up the connection.
Resolution order for the RM URL at runtime:
1. Job.yarn_rm_url (preferred — survives connection edits/deletes)
2. Connection.yarn_rm_url fallback (if a future tool is added that
doesn't go through a Job)
3. YARN_RESOURCE_MANAGER_URL env var
Errors:
- YarnConfigError (HTTP 4xx semantics) when URL is missing/malformed
- YarnError for HTTP 4xx/5xx from the RM, network failures, missing
state field, or unparseable log responses
10 new tests in test_yarn_client.py cover the REST surface:
success, 404, 5xx, missing state field, env-var fallback, malformed
URL, log 404 with log-aggregation hint, kill PUT body shape, and
httpx connection-error wrapping.
This commit is contained in:
@@ -11,11 +11,25 @@ def test_job_roundtrip():
|
||||
queue="default",
|
||||
submit_time=datetime(2026, 6, 24, 10, 0, 0),
|
||||
connection="prod-yarn",
|
||||
yarn_rm_url="http://rm:8088",
|
||||
)
|
||||
dumped = job.model_dump()
|
||||
assert dumped["job_id"] == "abc123"
|
||||
assert dumped["application_id"] == "application_17400000001"
|
||||
assert dumped["connection"] == "prod-yarn"
|
||||
assert dumped["yarn_rm_url"] == "http://rm:8088"
|
||||
|
||||
|
||||
def test_job_yarn_rm_url_optional():
|
||||
job = Job(
|
||||
job_id="j1",
|
||||
application_id="application_1",
|
||||
script_path="/tmp/x.py",
|
||||
queue="default",
|
||||
submit_time=datetime(2026, 6, 24),
|
||||
connection="dev",
|
||||
)
|
||||
assert job.yarn_rm_url is None
|
||||
|
||||
|
||||
def test_job_status_default_raw():
|
||||
@@ -63,6 +77,26 @@ def test_pending_submission_defaults_to_pending_status():
|
||||
assert p.error is None
|
||||
assert p.job_id is None
|
||||
assert p.application_id is None
|
||||
assert p.yarn_rm_url is None # default for connections without one set
|
||||
|
||||
|
||||
def test_pending_submission_carries_yarn_rm_url_snapshot():
|
||||
"""snapshotting yarn_rm_url lets prepare/confirm survive connection edits."""
|
||||
p = PendingSubmission(
|
||||
pending_id="p_x",
|
||||
connection="prod",
|
||||
master="yarn",
|
||||
deploy_mode="cluster",
|
||||
yarn_rm_url="http://rm-prod:8088",
|
||||
script_path="/tmp/j.py",
|
||||
queue="default",
|
||||
executor_memory="4G",
|
||||
executor_cores=2,
|
||||
num_executors=2,
|
||||
spark_conf={},
|
||||
created_at=datetime(2026, 6, 24),
|
||||
)
|
||||
assert p.yarn_rm_url == "http://rm-prod:8088"
|
||||
|
||||
|
||||
def test_pending_submission_can_record_outcome():
|
||||
|
||||
Reference in New Issue
Block a user