[ 
https://issues.apache.org/jira/browse/HDDS-16889?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-16889:
------------------------------
    Attachment: MC-4-reject-activate-deactivate-of-closed-pipeline.patch

> Admin pipeline activate reopens a CLOSED pipeline, and deactivate moves a 
> CLOSED or ALLOCATED pipeline to DORMANT
> -----------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16889
>                 URL: https://issues.apache.org/jira/browse/HDDS-16889
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> MC-4-reject-activate-deactivate-of-closed-pipeline.patch, 
> TestBugMC4ActivateClosedPipeline.java
>
>
> h3. Mechanism
> {{PipelineManagerImpl.activatePipeline}} and 
> {{PipelineManagerImpl.deactivatePipeline}}, called by 
> {{SCMClientProtocolServer}} for {{ozone admin pipeline activate|deactivate}} 
> after the admin check, take the pipeline manager write lock and call 
> {{PipelineStateManager.updatePipelineState}} without looking at the current 
> state. {{PipelineStateMap.updatePipelineState}} has no transition check 
> either, so any state can become OPEN or DORMANT, and a move to OPEN puts the 
> pipeline back into the list used for container allocation.
> Everywhere else CLOSED is terminal: {{openPipeline}} refuses a CLOSED 
> pipeline ("Closed pipeline can not be opened"), the scrubber only deletes a 
> CLOSED pipeline, and the Javadoc of the two methods says "Activates a dormant 
> pipeline" and "Deactivates an active pipeline". By the time a pipeline is 
> CLOSED its containers have been finalized, and the way back to a writable 
> pipeline is a new one from the background pipeline creator.
> h3. Trigger
> # A Ratis THREE or EC pipeline is OPEN and has a container.
> # An admin deactivates it (for example while debugging it).
> # SCM closes the pipeline on its own: a datanode of it goes stale and 
> {{StaleNodeHandler}} closes its pipelines (other SCM side closes are 
> {{DeadNodeHandler}}, an admin {{ozone admin pipeline close}} and the 
> scrubber's unregistered node rule). The container goes to CLOSING and the 
> pipeline to CLOSED. It stays listed until the scrubber deletes it after 
> {{ozone.scm.pipeline.destroy.timeout}} (default 300s).
> # Within that window the admin runs {{ozone admin pipeline activate}} to undo 
> step 2. The CLOSED pipeline becomes OPEN.
> Variants: {{deactivate}} on a CLOSED pipeline makes it DORMANT; 
> {{deactivate}} on an ALLOCATED pipeline makes it DORMANT, after which neither 
> the datanode report ({{openPipeline}} only opens ALLOCATED pipelines) nor the 
> ALLOCATED timeout of the scrubber acts on it.
> h3. Impact
> * A reopened CLOSED pipeline is offered to 
> {{ContainerManagerImpl.allocateContainer}} again, which creates a new OPEN 
> container on it (run). The scrubber skips it because it is no longer CLOSED, 
> so {{removePipeline}} never sends the {{ClosePipelineCommand}} to its 
> datanodes (run, past the destroy timeout).
> * A CLOSED pipeline made DORMANT is skipped by the scrubber and 
> {{deletePipeline}} refuses it with "is not yet closed" (run).
> * How long this lasts depends on who closed the pipeline. When the pipeline 
> was closed because of a datanode close action, the datanodes resend that 
> action with every heartbeat until the pipeline is removed from them 
> (HDDS-9735), so SCM closes it again within one heartbeat interval and the 
> container allocated in between goes to CLOSING. It stays OPEN, or DORMANT, 
> only when SCM closed the pipeline itself (stale or dead node, admin close, 
> scrubber), for example an EC pipeline closed by {{StaleNodeHandler}}; then it 
> lasts until an admin closes it. These points are argued from code; the 
> reproduction does not model datanodes.
> * Only an admin can trigger it, and no data loss was shown.
> h3. Reproduction
> PASS, deterministic, unmodified source. 
> [^TestBugMC4ActivateClosedPipeline.java] ({{hadoop-hdds/server-scm}}) uses a 
> real {{PipelineManagerImpl}} and {{ContainerManagerImpl}} on RocksDB with a 
> mock node manager, the single node HA stub and a mock clock, and makes the 
> calls SCM makes for a stale node close, the admin commands, container 
> allocation and the scrubber. The three tests show a CLOSED pipeline reopened 
> and given a new OPEN container and still OPEN after the destroy timeout 
> scrub, a CLOSED pipeline made DORMANT that the scrubber and 
> {{deletePipeline}} do not remove, and an ALLOCATED pipeline left DORMANT 
> after a datanode report and the ALLOCATED timeout scrub. A passing test means 
> the defect is present.
> h3. Patch
> [^MC-4-reject-activate-deactivate-of-closed-pipeline.patch], against 
> b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
> 6818707698c.
> {{activatePipeline}} throws {{InvalidPipelineStateException}} for a CLOSED 
> pipeline, like {{openPipeline}}. Activating an ALLOCATED pipeline stays 
> allowed, because it is used to force open pipelines (the freon 
> {{SCMThroughputBenchmark}} and {{TestContainerCommandsEC}}). 
> {{SCMThroughputBenchmark}} activates every listed pipeline, so a CLOSED 
> pipeline in that list now makes it fail; that is unlikely because its fake 
> datanodes never report the pipelines, which therefore stay ALLOCATED (not 
> run). {{deactivatePipeline}} accepts only OPEN (DORMANT stays a no op) and 
> throws for ALLOCATED and CLOSED. The ALLOCATED rejection is separate from the 
> CLOSED one and can be dropped for the narrowest patch. The check runs on the 
> leader under the same write lock as the state update, before the update is 
> submitted, so the replicated apply path, the Ratis log and the RPC messages 
> are unchanged. Operators now get an error for these transitions instead of a 
> silent state change. An unknown pipeline ID now fails with 
> {{PipelineNotFoundException}}, as for {{pipeline close}}, instead of 
> succeeding silently.
> {{TestContainerCommandsEC#testCreateRecoveryContainerAfterDNRestart}} 
> activated its EC pipeline again after a datanode restart, when 
> {{StaleNodeHandler}} had already closed it. The write chunk that follows goes 
> straight to the datanode, which does not check the SCM pipeline state, so the 
> patch drops that activate call.
> Covered by {{testActivateAndDeactivateRejectClosedPipeline}} and 
> {{testDeactivateRejectsAllocatedPipeline}} (which also asserts the unknown ID 
> case) in the existing {{TestPipelineManagerImpl}}; 
> {{testDeactivatePipelineShouldFailOnFollower}} now opens its pipeline first 
> so that it still tests the follower path. Without the change both new tests 
> fail because nothing is thrown. With it all test classes of the SCM pipeline 
> package and {{TestSCMClientProtocolServer}} (269 tests) and the whole 
> {{TestContainerCommandsEC}} class (54 tests) pass, and checkstyle reports no 
> violations. With the patch the three reproduction tests fail at the admin 
> call with {{InvalidPipelineStateException}}.
> Found by TLA+ model checking and code review of the SCM pipeline lifecycle 
> paths under HDDS-15926, on commit b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. 
> Checked against HDDS issues and apache/ozone pull requests for duplicates 
> before filing. The attached patch is a proposal for review. Generated with 
> Specula (Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to