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

Siyao Meng updated HDDS-16917:
------------------------------
    Attachment: TestBugMC4CrashBetweenCommitMoves.java

> SCM that crashes while it applies a root CA rotation commit cannot start 
> again, the sub CA directory switch is not completed on start
> -------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16917
>                 URL: https://issues.apache.org/jira/browse/HDDS-16917
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> MC-4-complete-interrupted-rotation-commit-on-scm-start.patch, 
> TestBugMC4CrashBetweenCommitMoves.java
>
>
> h3. Mechanism
> {{RootCARotationHandlerImpl.rotationCommit}} switches to the new sub CA 
> certificate in three separate steps: move {{scm/sub-ca}} to 
> {{scm/sub-ca-previous}}, move {{scm/sub-ca-next}} to {{scm/sub-ca}}, write 
> the new certificate ID to the VERSION file. SCM start reads only the VERSION 
> file and {{scm/sub-ca}} and has no step that completes or reverts a switch 
> that stopped in between.
> * Stopped after the first move: {{scm/sub-ca}} does not exist. 
> {{DefaultCAServer.init}} for the sub CA throws {{IllegalStateException: 
> INTERMEDIARY_CA Should not be in Initialize State during startup.}} in the 
> {{StorageContainerManager}} constructor, on the primary SCM and on any other 
> SCM.
> * Stopped after the second move: {{scm/sub-ca}} holds the new certificate, 
> the VERSION file names the old one. {{SCMCertificateClient.getCertificate}} 
> returns null. On the primary SCM the constructor fails with a 
> {{NullPointerException}} in {{persistPrimarySCMCerts}}. On any other SCM the 
> constructor returns without a certificate, and 
> {{RootCARotationManager.shouldSkipRootCert}}, the first call of the commit 
> when it is applied again, throws a {{NullPointerException}} on the missing 
> trust chain. Inside {{SCMStateMachine.applyTransaction}} that terminates the 
> SCM (the replay itself is argued, not run).
> The commit can never be applied again to finish the switch, because the SCM 
> fails before or while it replays the RAFT log.
> The same three steps are in the release tags ozone-1.4.0, 1.4.1, 2.0.0, 
> 2.1.0, 2.1.1, 2.1.2, 2.2.0 and 2.2.1, and SCM start does not look at 
> {{scm/sub-ca-previous}} in any of them (read with git grep, not run).
> h3. Trigger
> Secure SCM HA with {{hdds.x509.ca.rotation.enabled=true}} (default false). An 
> SCM process dies (kill, OOM, power loss) between the first directory move and 
> the VERSION write of {{rotationCommit}}. The window is two renames and one 
> small file write, once per rotation and SCM. With the default 
> {{hdds.x509.max.duration}} of P1865D a rotation runs once in about five years.
> The second state is also reached without a process death in that window. An 
> SCM that is restarted between its prepare ack and the commit terminates after 
> both moves and before the VERSION write (HDDS-16913). An {{IOException}} from 
> the VERSION write makes the handler call {{scm.shutDown}} on the thread that 
> applies the log entry, which leaves the SCM hung until it is killed 
> (HDDS-16911), and the next start then finds the second state (argued, not 
> run).
> h3. Impact
> The SCM fails on every later start until an operator moves the directories or 
> edits the VERSION file by hand. No key or certificate is lost: the old pair 
> is in {{scm/sub-ca-previous}} and the new pair in {{scm/sub-ca-next}} or 
> {{scm/sub-ca}}. {{ozone repair scm cert recover}} does not handle these 
> directories.
> If the SCM was a follower, the other SCMs finish the rotation and the ring 
> runs with one member less. If it was the leader, the other SCMs still apply 
> the commit, but the committed request, which only the leader sends and only 
> right after the commit, is not sent (read, not run). The rotation is then 
> also left unfinished on the other SCMs, see HDDS-16912.
> h3. Reproduction
> PASS, deterministic, unmodified source. 
> [^TestBugMC4CrashBetweenCommitMoves.java] ({{hadoop-ozone/integration-test}}) 
> runs {{scm --init}} of a secure SCM with a MiniKdc, stages a new sub CA 
> certificate with the real certificate client and a new root CA (the leader 
> that signs the request is a mock backed by that CA), performs the directory 
> moves of {{rotationCommit}} up to the crash point and then constructs the 
> {{StorageContainerManager}} again. Both crash points fail as described above. 
> A passing test means the defect is present.
> The test SCM is the primary. For an SCM that is not the primary the same 
> states were given to the real constructor after the primary SCM ID in the 
> VERSION file was changed (run, not part of the attached reproduction): after 
> the first move the constructor throws the same {{IllegalStateException}}, 
> after the second move it returns with {{getCertificate()}} null and 
> {{shouldSkipRootCert}} throws the {{NullPointerException}}.
> h3. Patch
> [^MC-4-complete-interrupted-rotation-commit-on-scm-start.patch], against 
> b9b33851b20fa11baf4b07982de9afb8ea44f85a. It also applies to master at 
> 5108acff480. The patch is made against that commit alone, other fixes 
> proposed for the same code may need a manual merge.
> Before the SCM certificate client is created, 
> {{StorageContainerManager.initializeCertificateClient}} calls the new 
> {{RootCARotationManager.completeInterruptedCommit}}. It acts only when the 
> certificate named in the VERSION file is found in {{scm/sub-ca-previous}} and 
> is not the one in {{scm/sub-ca}}, which only an unfinished commit leaves 
> behind. It then moves {{scm/sub-ca-next}} to {{scm/sub-ca}} if that has not 
> happened, writes the ID of the certificate in {{scm/sub-ca}} to the VERSION 
> file and deletes {{scm/sub-ca-previous}}. The switch is completed and not 
> reverted because the commit entry is already committed in the RAFT log. When 
> the commit and committed entries are applied again they are skipped, since 
> the SCM already trusts the new root certificate. If {{scm/sub-ca}} is missing 
> and nothing is staged in {{scm/sub-ca-next}} the directories are left as they 
> are and the start fails as before (run). A certificate that is staged but was 
> never committed is not touched.
> Covered by four cases of {{testScmStartCompletesInterruptedRotationCommit}} 
> in the existing {{TestSecureOzoneCluster}}: the two crash points, a 
> certificate that is staged and not committed, and a {{scm/sub-ca-previous}} 
> that is a copy of the directory in use. In the last two the SCM must start on 
> its old certificate and leave the directories alone. Without the change the 
> two crash point cases fail with the two exceptions above. With it the new 
> test, {{testSecureScmStartupFailure}} of the same class, 
> {{TestRootCARotationManager}} and {{TestScmInvokerCodeGenerator}} pass (18 
> tests) and checkstyle is clean. The other tests of {{TestSecureOzoneCluster}} 
> were not run. With the patch both cases of the reproduction fail, because the 
> SCM starts.
> Found by TLA+ model checking and code review of the SCM root CA rotation 
> paths under HDDS-15926, on commit b9b33851b20fa11baf4b07982de9afb8ea44f85a. 
> 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