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

Siyao Meng updated HDDS-17016:
------------------------------
    Attachment: MC-12-enforce-volume-failure-tolerance-on-every-check.patch

> Datanode with more failed volumes than tolerated keeps serving after restart, 
> then stops on a later volume check that finds nothing wrong
> -----------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17016
>                 URL: https://issues.apache.org/jira/browse/HDDS-17016
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Major
>         Attachments: 
> MC-12-enforce-volume-failure-tolerance-on-every-check.patch, 
> TestBugMC12ToleranceAtStartup.java
>
>
> h3. Mechanism
> {{MutableVolumeSet.hasEnoughVolumes}} is only called from 
> {{handleVolumeFailures}}, and {{checkAllVolumes}} calls that only when the 
> check itself finds a new failure. Volumes that are failed directly through 
> {{failVolume}} are never compared with 
> {{hdds.datanode.failed.data.volumes.tolerated}} (or its metadata and DB 
> variants) at that point. At startup these are volumes rejected by 
> {{initializeVolumeSet}} (inconsistent or unloadable root), by 
> {{VersionEndpointTask.checkVolumeSet}} and by {{ContainerReader}}; at runtime 
> {{ScmHAFinalizeUpgradeActionDatanode}} does the same. The startup 
> {{checkAllVolumes}} in {{OzoneContainer.start}} then finds no new failure, so 
> the datanode starts and serves although it is over the limit.
> The callback of {{checkVolumeAsync}} calls {{handleVolumeFailures}} even when 
> the check found nothing. The next on demand check of any healthy volume 
> (started by an I/O error on a container) therefore finds the set over the 
> limit and calls {{handleFatalVolumeFailures}}, stopping the datanode at an 
> arbitrary later time.
> This dates from the introduction of the tolerance: the startup exception that 
> HDDS-9022 replaced with {{handleFatalVolumeFailures}} was also only thrown 
> from {{handleVolumeFailures}}.
> h3. Trigger
> A finite tolerance and a restart with more unusable volumes than tolerated, 
> for example volumes whose {{VERSION}} file is missing or corrupt. With the 
> default {{-1}} only one corner case is affected: {{ContainerReader}} at 
> startup or {{ScmHAFinalizeUpgradeActionDatanode}} at runtime fails every data 
> volume directly. The datanode then keeps running with no data volume, because 
> no healthy volume is left to start an on demand check.
> h3. Impact
> * The datanode serves with more failed volumes than the operator allowed, 
> until any later failure handling runs.
> * It is then stopped by a check that found nothing wrong, for example the on 
> demand check of a healthy volume.
> * Shown at unit level with a real volume set and checker and a mocked 
> {{DatanodeStateMachine}}; not exercised on a datanode registered with SCM.
> h3. Reproduction
> PASS on unmodified source. [^TestBugMC12ToleranceAtStartup.java] 
> ({{hadoop-hdds/container-service}}, target path and Maven command in the 
> class comment) sets the data volume tolerance to 1, formats three volumes 
> through {{StorageVolumeUtil.checkVolume}}, removes the {{VERSION}} file of 
> two, rebuilds the volume set with a real {{StorageVolumeChecker}} and runs 
> {{checkAllVolumes()}} as {{OzoneContainer.start}} does. Output: {{healthy=1 
> failed=2 tolerated=1}}, and the startup check makes no fatal call. 
> {{StorageVolumeUtil.onFailure}} on the healthy volume then logs "Not enough 
> volumes ... MaxVolumeFailuresTolerated: 1, ActiveVolumes: 1, FailedVolumes: 
> 2" and calls {{handleFatalVolumeFailures}} while the checked volume is still 
> healthy. A passing test means the defect is present.
> h3. Patch
> [^MC-12-enforce-volume-failure-tolerance-on-every-check.patch], against 
> cce1ca28944886218132f01c0e271c1fd0700078. It also applies to master at 
> 2536fe9cbc3.
> {{MutableVolumeSet.checkAllVolumes}} now also calls 
> {{handleFatalVolumeFailures}} when the check found no new failure but 
> {{hasEnoughVolumes()}} is false. {{OzoneContainer.start}} runs it on the 
> data, metadata and DB volume sets after all startup paths that fail volumes 
> directly, so an over limit datanode stops at startup, and the periodic check 
> covers direct failures at runtime. Stopping at startup is the intended 
> behaviour: HDDS-5219 threw "Don't have enough good volumes on startup", and 
> {{TestDatanodeHddsVolumeFailureToleration.testDNCorrectlyHandlesVolumeFailureOnStartup}}
>  expects a restart with two bad volumes and tolerance 1 to exit. Both only 
> work when the startup check itself finds the failures; the patch applies the 
> same rule to volumes that failed before that check. Calling 
> {{handleVolumeFailures}} unconditionally was not chosen because it would also 
> run the failed volume listener on every periodic check.
> Covered by {{testVolumesFailedOnLoadCountTowardsTolerance}} in the existing 
> {{TestVolumeSetDiskChecks}}: three volumes, two of them failed by 
> {{initializeVolumeSet}}, a checker that reports no failure. With tolerance 1 
> it expects one fatal call and without the change fails with "Wanted but not 
> invoked: datanodeStateMachine.handleFatalVolumeFailures()"; with tolerance 2 
> and with the default {{-1}} it expects none and passes either way. With the 
> change the volume, container, reader, upgrade, handler and state machine 
> suites (309 tests) and {{TestDatanodeHddsVolumeFailureToleration}} pass, and 
> checkstyle is clean. With the patch the reproduction fails at its check that 
> startup made no fatal call.
> *Upgrade note.* A datanode with a finite 
> {{hdds.datanode.failed.data.volumes.tolerated}} (or the metadata or DB 
> variant) that has more unusable volumes than tolerated now exits at startup 
> with "DatanodeStateMachine Shutdown due to too many bad volumes", instead of 
> serving until a later volume check stops it. Such a datanode will not come 
> back after the upgrade restart, so a rolling upgrade stalls at it. Before 
> upgrading, compare {{NumFailedVolumes}} of 
> {{VolumeHealthMetrics-DATA_VOLUME}} (and {{VolumeHealthMetrics-META_VOLUME}}, 
> {{VolumeHealthMetrics-DB_VOLUME}}) with the tolerance on each such datanode, 
> then repair or remove the failed directories or raise the tolerance. With the 
> default {{-1}} a datanode now also exits when every data volume has been 
> failed directly, instead of running with none.
> Found by TLA+ model checking and code review of the datanode volume and 
> per-disk RocksDB lifecycle paths under HDDS-15926, on commit 
> cce1ca28944886218132f01c0e271c1fd0700078. Checked against HDDS issues and 
> apache/ozone pull requests for duplicates before filing. Related but not 
> duplicates: HDDS-5219 (introduced the tolerance), HDDS-9022 (replaced the 
> startup exception with the fatal handler), HDDS-14871 (volume check latch 
> timeout) and HDDS-11770 (proposed default 0, abandoned). 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