Skip to content

fix(aws-batch): multi-node job nodes and Fargate platform checks - #1684

Merged
Satyam-Trivedi-ZS merged 2 commits into
stackshy:developmentfrom
Satyam-Trivedi-ZS:fix/aws-batch-followups-1669
Oct 23, 2026
Merged

Satyam-Trivedi-ZS merged 2 commits into
stackshy:developmentfrom
Satyam-Trivedi-ZS:fix/aws-batch-followups-1669

Conversation

@Satyam-Trivedi-ZS

@Satyam-Trivedi-ZS Satyam-Trivedi-ZS commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Builds on #1648 (merged); rebased onto development, so the diff is now only this PR's changes.

Summary

  • Multi-node parallel (MNP) jobs now have nodes. DescribeJobs resolves <jobId>#<n>, and ListJobs --multi-node-job-id lists the nodes instead of returning an empty list.
  • SubmitJob rejects nodeOverrides, and multi-node job definitions, on a job queue backed by Fargate. RegisterJobDefinition rejects nodeProperties together with the FARGATE platform capability.

Part of #1669

Item status

Item Status Evidence
MNP jobs run as a single job (no nodeDetails, empty ListJobs by multiNodeJobId) FIXED Multi-node parallel jobs: "You can address nodes using the #N notation (starting with 0) ... describe aws_batch_job_id#1". JobDetail nodeDetails (nodeIndex, isMainNode). ListJobs multiNodeJobId "lists all nodes that are associated with the specified job". JobSummary nodeProperties (isMainNode, numNodes, nodeIndex). Shapes checked against the botocore model.
SubmitJob doesn't reject overrides that don't fit the platform FIXED (the documented Fargate rules) SubmitJob nodeOverrides: "This parameter isn't applicable to jobs that are running on Fargate resources; use containerOverrides instead." RegisterJobDefinition type: "If the job is run on Fargate resources, then multinode isn't supported"; nodeProperties: "If the job runs on Fargate resources, then you must not specify nodeProperties". Job definitions on Fargate: type "Must be specified as container". The only documented client error is ClientException (HTTP 400).

The other override/platform rules from the issue are left as they were, and nongoals/batch.md lists them:

  • ecsPropertiesOverride against EC2: SKIPPED, it isn't a real rule. ecsProperties runs on both EC2 and Fargate. The rule that does exist ("can only be specified for jobs that are run on Amazon ECS resources", and the reverse for EKS) needs EKS compute environments, and the emulator doesn't model those (eksConfiguration isn't stored). The existing check that the job definition has ecsProperties / eksProperties stays.
  • MNP on Spot or UNMANAGED compute environments: UNVERIFIABLE. MNP compute environment considerations says they "aren't supported", but not whether SubmitJob rejects the job or leaves it stuck.
  • containerOverrides.instanceType / memory / vcpus on Fargate: SKIPPED. ContainerOverrides only says they "shouldn't be provided" or are "not supported", and doesn't say they're rejected.

Changes

  • services/batch/driver: new NodeDetails (NodeIndex, IsMainNode, NumNodes) on driver.Job. It is set only on a node.
  • providers/aws/batch/job_nodes.go (new):
    • Nodes are derived from the job record, the same way array children are in job_lifecycle.go, so there's no new stored state and nothing new for persist or snapshots.
    • A node renders the job's own view (status, reason, times, cancel/terminate flags), plus nodeDetails and the container / ecsProperties / eksProperties of the node range that covers its index (nodeOverrides already applied). It drops nodeProperties.
    • ListJobs by multiNodeJobId pages by node index with the same cursor token as array children. Every node shares the job's name, definition, creation time and status, so the first node decides status and filters, and the loop stays bounded by the page size.
    • fargateQueue / validateJobPlatform / validateJobDefinitionPlatform implement the Fargate rules. A queue counts as Fargate if a compute environment in its order has computeResources.type FARGATE or FARGATE_SPOT. A queue can't mix Fargate and EC2 (Job queues on Fargate).
  • providers/aws/batch/jobs.go: DescribeJobs falls back to node IDs, ListJobs routes multiNodeJobId to the node lister, and SubmitJob runs the platform check after resolving the queue and the definition.
  • server/aws/batch/types_jobs.go: wire translation only. JobDetail.nodeDetails and JobSummary.nodeProperties.
  • Events: real Batch documents "Batch Job State Change" per job (Job state change events) and says nothing about node events. Nodes send none, the same as the existing choice for array children. A test pins this.
  • docs/coverage/nongoals/batch.md updated and go generate ./... output committed.
  • Two existing override tests submitted MNP jobs to the Fargate-backed test queue. They now use an EC2 queue, so they keep testing the override rules and not the new platform rule.

Provider Coverage

  • AWS
  • Azure (n/a: AWS Batch only)
  • GCP (n/a)
  • OCI (n/a)

Checklist

  • All tests pass (go test -p 2 over every package, run in chunks of 30)
  • Linter: no new findings in touched packages (golangci-lint run --new-from-rev=origin/fix/aws-batch-parity-1496 on providers/aws/batch, server/aws/batch, services/batch: 0 issues)
  • Every provider the change applies to implements the same behavior (AWS only)
  • Integration tests added to cloudemu_test.go (not needed; provider and SDK wire tests cover it)
  • Unit tests added to provider test files
  • Regenerated docs (go generate ./...) and committed the result

Test Plan

New tests. They fail on 689b5506 and pass here; I checked by reverting the provider and wire changes:

  • providers/aws/batch/job_nodes_test.go:
    • TestMultiNodeJobDescribesEachNode: per-node nodeDetails, range container, and the lifecycle on a FakeClock through RUNNING to SUCCEEDED with exitCode 0. #3 and #-1 are omitted.
    • TestMultiNodeNodesFollowCancel
    • TestListJobsByMultiNodeJobID: order, numNodes, paging with maxResults 2, default RUNNING, filters, unknown and non-MNP IDs.
    • TestMultiNodeNodesSurviveSnapshot
    • TestSubmitJobRejectsMultiNodeOnFargate: FARGATE, FARGATE_SPOT, queue by ARN, and accepted on EC2.
    • TestRegisterMultiNodeRejectsFargatePlatform
    • TestMultiNodeNodesSendNoEvents is a guard, so it also passes on the base.
  • server/aws/batch/jobs_sdk_test.go (aws-sdk-go-v2):
    • TestSDKMultiNodeJobNodes
    • TestSDKSubmitJobRejectsNodeOverridesOnFargate: asserts *types.ClientException.

Commands:

  • go build ./...: ok.
  • go test -race ./providers/aws/batch/... ./server/aws/batch/... ./providers/aws/ ./persist/... ./internal/settle/...: all ok.
  • go test -p 2 over all packages in chunks of 30: all ok.
  • golangci-lint run --allow-parallel-runners --concurrency 2 --new-from-rev=origin/fix/aws-batch-parity-1496 on the touched packages: 0 issues.

Real-user check with the AWS CLI against cloudemu serve --async-settle -providers aws -aws-port 4601. Every call used --endpoint-url http://127.0.0.1:4601.

aws batch create-compute-environment --compute-environment-name ce-ec2 --type MANAGED --compute-resources type=EC2,...
aws batch create-compute-environment --compute-environment-name ce-fg  --type MANAGED --compute-resources type=FARGATE,...
aws batch create-job-queue --job-queue-name q-ec2 ... ; aws batch create-job-queue --job-queue-name q-fg ...
aws batch register-job-definition --job-definition-name mnp --type multinode --node-properties '{"numNodes":2,"mainNode":0,"nodeRangeProperties":[{"targetNodes":"0","container":{"image":"lead","command":["lead"]}},{"targetNodes":"1:","container":{"image":"worker","command":["work"]}}]}'
aws batch submit-job --job-name mpi --job-queue q-ec2 --job-definition mnp --node-overrides '{"numNodes":3,"nodePropertyOverrides":[{"targetNodes":"1:","containerOverrides":{"command":["work","--fast"]}}]}'
  • describe-jobs --jobs $ID $ID#0 $ID#1 $ID#2 $ID#3:
    • The parent has nodeProperties.numNodes 3 and no nodeDetails.
    • #0 has {"nodeIndex":0,"isMainNode":true} and image lead.
    • #1 and #2 have isMainNode: false, image worker and command ["work","--fast"].
    • #3 is omitted.
  • Polling a fresh job: the parent, #0 and #1 move together SUBMITTED, RUNNABLE, RUNNING (same startedAt), then SUCCEEDED.
  • list-jobs --multi-node-job-id $ID (default RUNNING, while running) returned both nodes with nodeProperties {isMainNode, numNodes: 2, nodeIndex}.
  • --job-status SUCCEEDED returned #0, #1 and #2, each with container.exitCode 0.
  • Paging with --max-results 2 --no-paginate returned #0 and #1 with nextToken MQ. Passing that token returned #2 with no token.
  • submit-job --job-queue q-fg --job-definition mnp --node-overrides '{"numNodes":3}' returned ClientException with exit 255. Raw HTTP is 400 with X-Amzn-Errortype: ClientException.
  • submit-job --job-queue q-fg --job-definition mnp returned ClientException.
  • register-job-definition --type multinode --platform-capabilities FARGATE ... returned ClientException.

Known limits

  • Every node, main or child, has the job's own status and times. Real Batch launches the child nodes once the main node is RUNNING, and a child node can finish or fail on its own. The emulator runs no workload, so there's nothing that would make a node diverge.
  • CancelJob / TerminateJob take the job ID, not a node ID. A node ID returns the existing "does not exist" ClientException.
  • Nodes send no EventBridge events of their own, matching array children. The docs don't say either way for MNP nodes.
  • The Fargate rejection happens at SubmitJob with an emulator-written message. The docs give the rule and the ClientException type, but not the exact message.
  • Note on fix(aws-batch): job lifecycle, scheduling policies, job definition type check and job state-change events #1648: the ListJobs reference says "The filter doesn't apply to child jobs in an array or multi-node parallel (MNP) jobs." fix(aws-batch): job lifecycle, scheduling policies, job definition type check and job state-change events #1648 applies filters to array children, as its review asked. The node listing here does the same, for consistency. If the maintainers read that sentence as "filters are ignored for child listings", both listings should change together.

@NitinKumar004 NitinKumar004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I checked

  • Both items of #1669 are covered. Per-node nodeDetails and ListJobs by multiNodeJobId are done. The Fargate platform rules the docs actually state are done, and the rules the docs don't pin down are written up in nongoals/batch.md.
  • Layering is clean. Nodes are derived in the provider from the stored job, so there is no new state and nothing for persist to carry. The driver gains a plain NodeDetails type. The wire layer only maps it to JobDetail.nodeDetails {nodeIndex, isMainNode} and JobSummary.nodeProperties {isMainNode, numNodes, nodeIndex}, which match the API reference shapes. go generate ./... leaves no diff.
  • Parity against the SubmitJob, RegisterJobDefinition, ListJobs, JobDetail and multi-node user guide pages:
    • #N addressing works, and the job's ID and ARN carry the node suffix.
    • Started, stopped and reason come from the job itself, as the guide says they come from the main node.
    • nodeOverrides is rejected on a Fargate queue ("use containerOverrides instead").
    • A multinode definition is rejected on a Fargate queue, and nodeProperties together with FARGATE is rejected at register time. Both return ClientException 400.
  • Gates on the PR head:
    • go build ./..., go vet and go test -p 1 on providers/aws/batch, server/aws/batch, services/batch and persist all pass. -race passes on the two batch packages.
    • gofmt is clean, go mod tidy and go generate leave no diff, and CI is all green.
    • golangci-lint without --new-from-rev reports 10 issues, the same 10 as the stacked base 689b550 (dupl, goconst, one G115 in job_lifecycle.go), only shifted by line. All 10 come from #1648. This PR adds none.
  • AWS CLI against cloudemu serve:
    • describe-jobs on $ID, #0, #2, #3: the parent has nodeProperties.numNodes 3 and no nodeDetails. Each node has the right nodeDetails, image and exitCode 0. #3 is omitted.
    • list-jobs by multiNodeJobId: the summaries are right, and --max-results 2 paging returns token MQ and then the last node.
    • The queue listing shows only the parent.
    • The three Fargate rejections work.
    • CancelJob on a node ID returns ClientException.
    • A node ID on a non-MNP job returns nothing from both describe and list.
    • With --async-settle, the parent and its nodes move together from SUBMITTED to RUNNING with the same startedAt. A non-zero mainNode (1) is reported correctly.
  • Terraform hashicorp/aws, aws_batch_job_definition type multinode: apply, plan exit 0, an update to the node range command, plan exit 0, destroy. A multinode definition with platform_capabilities = ["FARGATE"] fails apply with the new ClientException.
  • No AI attribution in the commits or the PR body.

High

None.

Medium

None.

Low

  1. A node ID that isn't in canonical form still resolves, and the response uses a different jobId. describe-jobs --jobs "$ID#01" "$ID#+1" returns the job "$ID#1" for both, because strconv.ParseInt accepts a leading zero and a + sign (job_nodes.go:78). A client that matches results to request IDs won't find its entry. Real Batch treats these as unknown IDs. Accept only 0 or a digit string with no leading zero. The array-child splitChildID from #1648 has the same issue, so fix both together.

  2. A node of an array MNP job carries the parent's arrayProperties. SubmitJob (#1648) accepts arrayProperties with a multinode definition. When that happens, renderNode starts from render(rec, -1), and each J#i node then reports arrayProperties {size 2, statusSummary} next to nodeDetails. A node isn't an array parent, so renderNode should clear ArrayProperties (job_nodes.go:118, next to NodeProperties = nil). Separately, it's worth checking whether real Batch rejects arrays on multinode definitions. I couldn't find a statement either way in the reference.

  3. fargateQueue treats a queue as Fargate if any of its compute environments is Fargate. It relies on the rule that a queue can't mix Fargate and EC2 (job_nodes.go:182), but the emulator doesn't enforce that rule: create-job-queue with ce-ec2 and ce-fg succeeds. On such a queue, every MNP submission is now rejected. This gap is in CreateJobQueue and UpdateJobQueue, not this PR. A follow-up should reject the mixed order there, so the assumption behind this check holds.

  4. Two points from the #1648 review are still open. They aren't part of this PR's diff, but the new node rendering makes them visible:

    • containerOverrides on a multinode definition is still dropped silently. Submitting with --container-overrides command=x shows container.command ["work"] on $ID#1 (jobs.go:396, mergeContainer returns unchanged when the definition has no containerProperties).
    • A node override aimed at part of a definition range is still rejected. Targeting "2:" against a "1:" range returns "doesn't cover any node range" (overrides.go:552).

    Both belong on #1648.

  5. The SubmitJob reference doesn't document a check that the definition's platformCapabilities matches the queue (an EC2 definition on a Fargate queue, or the reverse), and the PR doesn't add one. That's reasonable given the evidence. It's worth a line in nongoals/batch.md so readers know it is unchecked and not just unmentioned.

return "", 0, false
}

n, err := strconv.ParseInt(id[i+1:], 10, 32)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParseInt accepts 01 and +1, so describe-jobs --jobs "$ID#01" returns a job whose jobId is $ID#1, which isn't the ID that was asked for. Real Batch treats a non-canonical node ID as unknown. Accept only 0 or digits with no leading zero, and apply the same check to splitChildID.

suffix := nodeSeparator + strconv.Itoa(int(i))
j.JobID += suffix
j.JobARN += suffix
j.NodeProperties = nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SubmitJob accepts arrayProperties with a multinode definition. When it does, each J#i node inherits the parent's arrayProperties (size and statusSummary) from render(rec, -1). I'd clear j.ArrayProperties here as well, since a node isn't an array parent.

// compute environments have FARGATE or FARGATE_SPOT compute resources. A queue
// can't mix Fargate and EC2 compute environments (Job queues on Fargate, Batch
// User Guide), so one Fargate compute environment decides.
func (m *Mock) fargateQueue(q *driver.JobQueue) bool {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on a queue never mixing Fargate and EC2, but CreateJobQueue accepts order=1,ce-ec2 order=2,ce-fg today, and on that queue every MNP submission is rejected. That's a CreateJobQueue/UpdateJobQueue gap rather than one in this PR. A follow-up that rejects the mixed order would make this check hold.

DescribeJobs now resolves node job IDs ("<jobId>#<n>") and ListJobs with
multiNodeJobId lists the job's nodes instead of an empty list. Each node
carries nodeDetails (nodeIndex, isMainNode) and the container, ECS or EKS
properties of the node range that covers it; ListJobs summaries carry
nodeProperties (isMainNode, numNodes, nodeIndex).

Nodes are derived from the job record, the same way array children are,
so there is no new stored state. Every node follows the job's lifecycle
and, like array children, sends no state-change events of its own.
SubmitJob now returns a ClientException when nodeOverrides is sent to a
job queue whose compute environments are FARGATE or FARGATE_SPOT, or when
a multi-node job definition is submitted to one. The SubmitJob reference
says nodeOverrides "isn't applicable to jobs that are running on Fargate
resources; use containerOverrides instead", and the RegisterJobDefinition
reference says multinode isn't supported for jobs run on Fargate.

RegisterJobDefinition also rejects nodeProperties together with the
FARGATE platform capability, per the same reference.

The override tests that submit multi-node jobs now use an EC2 queue, so
they keep testing the override rules rather than the platform rule.
@Satyam-Trivedi-ZS
Satyam-Trivedi-ZS force-pushed the fix/aws-batch-followups-1669 branch from fc2eaf1 to fff15af Compare October 23, 2026 20:22
@Satyam-Trivedi-ZS
Satyam-Trivedi-ZS marked this pull request as ready for review October 23, 2026 20:22
@Satyam-Trivedi-ZS
Satyam-Trivedi-ZS merged commit b50fd72 into stackshy:development Oct 23, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants