Siyao Meng created HDDS-17423:
---------------------------------

             Summary: SCM bootstrapped into a ring that is not finalized starts 
finalized, finalizes datanodes without an admin finalize and gets them fenced
                 Key: HDDS-17423
                 URL: https://issues.apache.org/jira/browse/HDDS-17423
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-1-bootstrapped-scm-adopts-ring-apparent-version.patch, 
TestBugMC1BootstrappedScmInPrefinalizedRing.java

h3. Mechanism
{{StorageContainerManager.scmBootstrap}} takes only the cluster ID and the 
primary SCM ID from the running ring and writes a new VERSION file through 
{{new SCMStorageConfig(conf)}}, whose apparent version is 
{{HDDSVersion.SOFTWARE_VERSION}} unless the testing key 
{{testing.hdds.scm.init.apparent.version}} is set. {{ScmVersionManager}} is 
built from the VERSION file only, and the snapshot path 
({{RatisBasedVersionManager.finalizeFromSnapshotIfRequired}}) and the FINALIZE 
apply can only raise the version. Nothing on the bootstrap or start path brings 
the new SCM down to the version of the other SCMs, so an SCM added while the 
ring is upgraded but not finalized runs finalized ({{needsFinalization() == 
false}}) next to SCMs that are not.

h3. Trigger
# All SCMs and datanodes run the new software and are not finalized yet. Today 
this is the state of a ring upgraded from 2.2.x to this code (apparent version 
is the legacy layout version); with zero downtime upgrade it is the state 
between the rolling upgrade of the binaries and the admin finalize.
# An SCM is added with {{ozone scm --bootstrap}}, with production configuration.
# The datanodes are configured with the new SCM, and the new SCM becomes leader 
(election or {{ozone admin scm transfer}}).

h3. Impact
* As leader, the new SCM sends {{FinalizeVersionCommand}} to every datanode 
({{SCMNodeManager.sendFinalizeToDatanodeIfNeeded}}). The datanodes finalize 
although no admin finalize was issued and the other SCMs are still not 
finalized, so the downgrade option is lost (run).
* A finalized datanode that restarts is rejected by each SCM that is not 
finalized ({{SCMNodeManager.shouldFenceDatanode}}, apparent version above SCM) 
and shuts down (run, three rejections, datanode stopped).
* For an upgrade from a release that already has the zero downtime upgrade 
versions, the new SCM also rejects every datanode still on the previous 
release, which the other SCMs accept (run by sending the registration request 
of such a datanode to the SCM datanode protocol server, since no such release 
exists yet).
* This lasts until an admin finalize. No data loss is expected.
* Releases 1.3.0 to 2.2.1 have the same root cause: {{scmBootstrap}} 
initializes the new SCM at the newest layout version, so as leader it sends 
{{FinalizeNewLayoutVersionCommand}} to the datanodes of a cluster that is not 
finalized. Datanodes are not fenced there, because registration checks only the 
software layout version (read from code at the release tags, not run).

h3. Reproduction
PASS on the unmodified commit, about 64 s. 
[^TestBugMC1BootstrappedScmInPrefinalizedRing.java] 
({{hadoop-ozone/integration-test}}) starts three SCMs and three datanodes at 
apparent version {{ZDU}} through the testing key, bootstraps a fourth SCM with 
{{MiniOzoneHAClusterImpl.bootstrapSCM}} without that key, adds it to the 
datanodes by reconfiguration and transfers leadership to it. Output on the 
unmodified code: the new SCM is at {{STREAM_PUTBLOCK_WITHOUT_RAFT (101)}} while 
the original SCMs are at {{ZDU (100)}}, all datanodes finalize to 101 with no 
finalize command, a restarted datanode logs "SCM rejected this datanode's 
registration" and stops, and a registration with software and apparent version 
100 is accepted by an original SCM and rejected by the new one. A passing test 
means the defect is present.

h3. Patch
[^MC-1-bootstrapped-scm-adopts-ring-apparent-version.patch], against 
5f7111a27ea07ce6b7bf5284a4213ebacb65f2ae. It also applies to master at 
e03ba37c41d. It targets master only; the release branches use the older layout 
version framework, where the same idea would need a separate change.

{{GetScmInfoResponseProto}}, which {{scmBootstrap}} already fetches from the 
leader, gets an optional {{apparentVersion}} field filled by 
{{SCMBlockProtocolServer.getScmInfo}}. When it is lower than the initial 
apparent version of the new VERSION file, {{scmBootstrap}} writes it instead. A 
value that is too low is safe, because the new SCM finalizes with the ring when 
it applies the FINALIZE log entry (run) or installs a snapshot of a finalized 
ring (from code). A finalized ring and an existing SCM directory are unchanged. 
The field is only in a read RPC response and not in the Raft log; a response 
without it (a leader without the patch) keeps today's behaviour. A ring version 
above this SCM's software version is not used and is logged as a warning.

Not covered: the FINALIZE log entry has no target version, so a FINALIZE entry 
from an earlier upgrade that is still in the retained Raft log would raise a 
replaying new SCM to its own software version. That cannot happen at this 
commit: the entry is written only by this code, and a ring is unfinalized here 
only during its first upgrade into this framework, from a release whose log has 
no such entry.

The existing {{queryUpgradeStatus}} RPC already returns the SCM apparent 
version, but it fails while SCM is in safe mode, so using it would make 
bootstrap fail in a case it supports today.

Covered by {{testBootstrappedScmJoinsRingAtRingApparentVersion}} in the 
existing {{TestScmHAFinalization}}: a ring at {{INITIAL_VERSION}} bootstraps an 
SCM without the testing key, which must join at {{INITIAL_VERSION}} and then 
finalize with the ring; a second SCM bootstrapped into the finalized ring must 
start at the software version. {{testGetScmInfoApparentVersion}} in the 
existing {{TestScmBlockLocationProtocolPB}} checks that a response without the 
field gives no version, so bootstrap keeps today's behaviour. Without the 
change it fails with {{expected: INITIAL_VERSION (0) but was: 
STREAM_PUTBLOCK_WITHOUT_RAFT (101)}}. With it, {{TestScmHAFinalization}}, 
{{TestStorageContainerManagerHA}}, {{TestDatanodeSCMNodesReconfiguration}}, 
{{TestFailoverWithSCMHA}}, {{TestMiniOzoneHACluster}}, 
{{TestSecureOzoneCluster}} and the SCM unit suites for the block protocol 
server, both block protocol translators, the version manager and SCM startup 
pass, and checkstyle is clean. With the patch the reproduction fails at its 
first assertion on the new SCM, because it starts at {{ZDU (100)}}.

Related: HDDS-17089 (an SCM restarted after an interrupted snapshot install 
stays behind its finalized DB) and HDDS-17090 (a lagging follower rejects 
datanodes the leader finalized) are different mechanisms and touch different 
code; the patches do not overlap.

Found by TLA+ model checking and code review of the zero downtime upgrade 
version gating in SCM, OM and the datanode write path under HDDS-15926, on 
commit 5f7111a27ea07ce6b7bf5284a4213ebacb65f2ae. 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
(v10.3.26#10030026)

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

Reply via email to