#919 smoke: claim 18 step 7, a failed destroy leg writes no tombstone - #942
Merged
Conversation
#901 (PR #920) changed what an apply WRITES: a create_before_destroy replace whose destroy leg fails no longer records the deposed, still-running object as destroyed. It landed with fake-backed tests only. This is the emulator step that reads the record back off disk, after step 4's control has already shown that an ordinary replace DOES write a tombstone, so an empty list here cannot pass by never having replaced anything. The mechanism, confirmed against the pinned floci image with the plain CLI and no tofu in the loop before it was written into the step: - disable_api_termination does not work here. floci accepts the ModifyInstanceAttribute call, reads the attribute back as false, and terminates the instance anyway. (The provider at v6.59.0 would not have cleared it either - resourceInstanceDelete only does that under force_destroy - but the emulator never enforces it, so the leg would not fail.) - An IAM deny does. With FLOCI_SERVICES_IAM_ENFORCEMENT_ENABLED=true, a role granted Allow * plus Deny ec2:TerminateInstances can RunInstances and CreateTags, and TerminateInstances answers "AccessDenied ... User is not authorized to perform: ec2:TerminateInstances" with the instance still running. So the scenario exports FLOCI_IAM_ENFORCEMENT=true before stack_up (the harness's "test" key keeps bypassing the filter, so steps 1-6 run exactly as before), and step 7 creates the no-terminate role, proves its fence on a throwaway instance with the CLI, then applies a third replace under the role with create_before_destroy on. The apply exits non-zero; the record's identity.import_id is the replacement, deposed[] is the old instance, and tombstone[] carries step 4's two entries and not the old instance; the CLI reads the old instance as running; and the next plan proposes "aws_instance.web (deposed object ...) will be destroyed" rather than pruning it. BREAK=1 now has two arms in one run. The step-5 duplicate arm no longer tears down and exits: it terminates the duplicate and deletes its markers (confirmed with the CLI that an untagged terminated instance leaves describe-instances, describe-tags and the tagging API), so step 7's arm runs on the same estate. Step 7's arm patches the record to list the running, deposed instance under tombstone - the entry the write side produced before #901 - and the read must catch it; then the patch is reverted before teardown. Step 7 sits before teardown rather than directly after step 4 because the recovery apply that would be needed to converge the estate for step 5 destroys a deposed object and, on today's main, records no tombstone for it (#938), so the shadow-arm plan that follows would refuse on the lingering tag. Placing the deposed-alive state last means only the teardown has to deal with it, and the teardown handles deposed objects. Measured on FLOCI_PORT=4750 against sha256:a39185cc: $ bash live/smoke/smoke.sh a-shadow-is-not-a-claimant === 7. a failed destroy leg writes no tombstone === aws: [ERROR]: An error occurred (AccessDenied) when calling the TerminateInstances operation: User is not authorized to perform: ec2:TerminateInstances aws_instance.web: Creation complete after 10s [id=i-8bdd7bc5f639c6f07] Error: terminating EC2 Instance (i-ab39d21e298740497): operation error EC2: TerminateInstances, https response error StatusCode: 403, ... i-ab39d21e298740497 is running (the deposed object, the destroy the platform refused); i-8bdd7bc5f639c6f07 is its replacement identity.import_id = i-8bdd7bc5f639c6f07 (the replacement, live) deposed[] = i-ab39d21e298740497 (the old object, live, the destroy that was refused) tombstone[] = i-2272f959adbca418b i-67c9f4ac99301f1d8 # aws_instance.web (deposed object d6d3e90b) will be destroyed PASS: smoke scenario 'a-shadow-is-not-a-claimant' - every claim held (smoke v1.0.0) $ BREAK=1 bash live/smoke/smoke.sh a-shadow-is-not-a-claimant -> caught - the plan refused with "Two live resources claiming one address", naming both i-46adb6f07004367d2 and i-7a5e2cf4e369816a4. ... -> caught - the record named the running, deposed i-46adb6f07004367d2 as destroyed, and the read refused it. ... PASS: smoke scenario 'a-shadow-is-not-a-claimant' - every claim held (smoke v1.0.0) Docs: claims.md's claim 18 steps and BREAK paragraph, the table's minute count, and live/smoke/README.md's entry. Refs #901, #920, #900, #854, #670, #938. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXQqcqYiLU513jwXziuQnh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #919.
What this adds
A step on
a-shadow-is-not-a-claimant(claim 18) that reads the record back off the emulator after acreate_before_destroyreplace whose destroy leg failed, the write-side change #901 (PR #920) landed with fake-backed tests only. Step 4 is the control: it already shows an ordinary replace DOES write a tombstone, so an empty list here cannot pass vacuously.The mechanism, confirmed with the plain CLI first
The issue named two candidates. Only one works on the pinned image:
disable_api_terminationModifyInstanceAttribute, reads the attribute back asfalse, terminates anyway. Dead on this image. (The provider at v6.59.0 would not have cleared it either:resourceInstanceDeleteonly does that underforce_destroy.)ec2:TerminateInstancesAllow *+Deny ec2:TerminateInstancescanRunInstancesandCreateTags, andTerminateInstancesanswersAccessDenied ... User is not authorized to perform: ec2:TerminateInstanceswith the instance stillrunning.So the scenario exports
FLOCI_IAM_ENFORCEMENT=truebeforestack_up(the harness'stestkey bypasses the filter, so steps 1 to 6 run exactly as before) and step 7 creates ano-terminaterole, proves its fence on a throwaway instance with the CLI, then applies a third replace under the role.What step 7 asserts
Creation completeline is present, and the error is the platform's denial of the destroy legrunningidentity.import_idis the replacement,deposed[]is the old instance,tombstone[]still carries step 4's two entries and does not carry the old instanceaws_instance.web (deposed object ...) will be destroyedrather than pruning it; a plan that exits 0 without mentioning the deposed instance fails the stepBREAK=1 now runs two arms on one estate
The step-5 duplicate arm used to tear down and
exit 0. It now terminates the duplicate and deletes its markers (confirmed with the CLI that an untagged terminated instance leavesdescribe-instances,describe-tagsand the tagging API), so step 7's arm can run. Step 7's arm patches the record to list the running, deposed instance undertombstone, the entry the write side produced before #901, and the read must catch it. The patch is reverted before teardown.Placement
Step 7 sits before teardown rather than directly after step 4. Converging the estate for step 5 would need a recovery apply that destroys the deposed object, and on today's main that apply records no tombstone (#938), so the shadow-arm plan would refuse on the lingering tag. With the deposed-alive state last, only the teardown has to deal with it, and it does.
Measured
Both arms,
FLOCI_PORT=4750, pinned imagesha256:a39185cc:Docs updated: claim 18's step list and BREAK paragraph in
site/content/docs/claims.md, the table's minute count, and the entry inlive/smoke/README.md.Refs #901, #920, #900, #854, #670, #938.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KXQqcqYiLU513jwXziuQnh