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

Siyao Meng updated HDDS-17221:
------------------------------
    Attachment: TestBugCR8MaintenanceEndNotUpdated.java

> Starting maintenance again on a datanode already in maintenance reports 
> success but keeps the old maintenance end
> -----------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17221
>                 URL: https://issues.apache.org/jira/browse/HDDS-17221
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> CR-8-update-maintenance-end-on-repeated-maintenance.patch, 
> TestBugCR8MaintenanceEndNotUpdated.java
>
>
> h3. Mechanism
> {{NodeDecommissionManager.startMaintenance}} computes the end from {{--end}}, 
> but for a node in ENTERING_MAINTENANCE or IN_MAINTENANCE it only logs 
> "Nothing to do" and returns. The new end is dropped and no 
> {{DatanodeAdminError}} is added, so {{startMaintenanceNodes}} returns an 
> empty error list. With {{--force}} omitted the result is the same: 
> {{checkIfMaintenancePossible}} only leaves the node out of its capacity 
> check, and the node still reaches the same branch. The method carries a TODO 
> from the first version of the feature: "If startMaintenance is called on a 
> host already in maintenance, then we should update the end time?".
> The monitor reads the end from the node status ({{operationalStateExpired}}) 
> and ends maintenance at the old end. {{putIntoMaintenance}} carries the old 
> end over to IN_MAINTENANCE.
> h3. Trigger
> # {{ozone admin datanode maintenance --end=1 <dn>}}.
> # Before that hour has passed, {{ozone admin datanode maintenance --end=100 
> <dn>}} to extend, with or without {{--force}}.
> # At the first end the monitor puts the node back to IN_SERVICE.
> No document or test describes what a repeated request should do. 
> {{testNodesCanBePutIntoMaintenanceAndRecommissioned}} repeats the request 
> with the same end and only checks that no error is raised.
> h3. Impact
> * The extension is acknowledged ("Entering maintenance mode on datanode(s)" 
> with no errors) but ignored. Maintenance ends at the end of the first 
> request, and the operator is not told.
> * If the node is still down at that time it becomes a normal dead node and 
> its containers are re-replicated, which is what maintenance is meant to avoid 
> (argued from code and docs, not run). No data is at risk.
> * The workaround, recommission followed by a new maintenance request, loses 
> the new request if both are sent within one monitor interval, which is 
> HDDS-17220. With this patch that case keeps the node in maintenance with the 
> new end, because the branch now puts the node back into the monitor and so 
> takes it out of the cancelled queue.
> h3. Reproduction
> PASS on the unmodified source, deterministic. 
> [^TestBugCR8MaintenanceEndNotUpdated.java] ({{hadoop-hdds/server-scm}}) uses 
> the setup of {{TestNodeDecommissionManager}} (with a mocked 
> {{ReplicationManager}}, so it also runs on master) and calls 
> {{startMaintenanceNodes}}, the method {{SCMClientProtocolServer}} calls. One 
> test uses only that call. The other shows the effect without waiting an hour: 
> it moves the end left by the first request to 2 seconds from now with 
> {{setNodeOperationalState}}, requests 100 hours, and runs the monitor after 
> the old end. Output on the pin:
> {noformat}
> first window shortened to 2s: state=ENTERING_MAINTENANCE end=1792555380 (2s 
> from now)
> maintenance --end=100 --force: state=ENTERING_MAINTENANCE end=1792555380 (2s 
> from now)
> monitor run after the old end: state=IN_SERVICE end=0 (none)
> maintenance --end=1: state=ENTERING_MAINTENANCE end=1792558982 (3600s from 
> now)
> maintenance --end=100 --force: state=ENTERING_MAINTENANCE end=1792558982 
> (3600s from now)
> maintenance --end=100: state=ENTERING_MAINTENANCE end=1792558982 (3600s from 
> now)
> {noformat}
> A passing test means the defect is present. With the patch both tests fail: 
> the end becomes 100 hours away and the node stays in maintenance.
> h3. Patch
> [^CR-8-update-maintenance-end-on-repeated-maintenance.patch], against 
> b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
> 20d95ed37fe.
> For a node already in maintenance, a request with {{--end}} now updates the 
> end and nothing else. The new {{NodeManager.setMaintenanceEnd}} 
> ({{NodeStateMap.updateMaintenanceEnd}} in {{SCMNodeManager}}) checks under 
> the node state write lock that the node is still in a maintenance state and 
> sets the end, keeping its current op state, so a monitor transition at the 
> same time is not undone. If the node has left maintenance in the meantime, 
> the request returns an error. A request without {{--end}} keeps the current 
> end: the server cannot tell an omitted {{--end}} from {{--end=0}}, and 
> clearing an end by accident would leave the node in maintenance indefinitely. 
> The node is then passed to {{monitor.startMonitoring}}, as 
> {{continueAdminForNode}} does for a node in maintenance, so a node the 
> monitor has just released is tracked again. The heartbeat reconciliation in 
> {{SCMNodeManager}} already compares the end with the datanode's persisted 
> value and sends it to the datanode. The new end reaches the datanode and the 
> follower SCMs with the next heartbeat; until then a leader change keeps the 
> old end. The TODO is removed. HDFS {{DatanodeAdminManager.startMaintenance}} 
> also updates the expiry of a node already in maintenance.
> The monitor's own write when a node moves from ENTERING_MAINTENANCE to 
> IN_MAINTENANCE ({{DatanodeAdminMonitorImpl.setNodeOpState}}) read the end and 
> wrote it back as two steps, which would have dropped an end stored in 
> between, so it now goes through a new {{NodeStateMap}} method that writes the 
> state and keeps the current end in one step under the same lock. Without 
> that, "no error" would still not mean that the new end is in effect.
> This patch and the patch of HDDS-17220 change the same lines of 
> {{startMaintenance}}, so whichever lands second needs a rebase: a plain {{git 
> apply}} of either one on top of the other fails on the hunk that removes the 
> TODO, while {{git apply -3}} and a merge of the two apply cleanly. The 
> changes themselves are independent, HDDS-17220 withdraws a queued 
> recommission for every decommission or maintenance request and writes no end, 
> this one updates the end of a node already in maintenance. With both applied, 
> the maintenance branch puts the node back into the monitor twice, which is 
> harmless because the tracked set is keyed on the datanode.
> Covered by {{testMaintenanceEndIsUpdatedForNodeInMaintenance}} in the 
> existing {{TestNodeDecommissionManager}} (ENTERING_MAINTENANCE and an 
> untracked IN_MAINTENANCE node, with and without {{--force}}, both tracked and 
> in the same state after a monitor run, a request without {{--end}} keeping 
> the end, and no end set on an IN_SERVICE node), and by 
> {{testNodeOperationalStateCanBeUpdatedKeepingTheEnd}} in {{TestNodeStateMap}} 
> for the write the monitor now uses. Without the change the first test fails 
> because the end stays 1 hour away. With it {{TestNodeDecommissionManager}}, 
> {{TestDatanodeAdminMonitor}}, {{TestNodeDecommissionMetrics}}, 
> {{TestNodeStateManager}}, {{TestNodeStateMap}} and {{TestSCMNodeManager}} 
> (134 tests) pass, {{TestDecommissionAndMaintenance}} (9 tests, mini cluster) 
> passes with this patch alone, and checkstyle is clean.
> Found by code review of the SCM datanode decommission and maintenance admin 
> paths, as part of the TLA+ verification effort 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 and Claude 
> Opus 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