Siyao Meng created HDDS-17226:
---------------------------------

             Summary: A recommission is silently lost when the datanode admin 
workflow is resumed before the next monitor run, and the cancelled request 
completes
                 Key: HDDS-17226
                 URL: https://issues.apache.org/jira/browse/HDDS-17226
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-12-keep-queued-recommission-when-resuming-admin.patch, 
TestBugCR12ResumedAdminDropsQueuedRecommission.java

h3. Mechanism
{{NodeDecommissionManager.recommission}} does not change the op state of the 
node. It calls {{DatanodeAdminMonitorImpl.stopMonitoring}}, which queues the 
node in {{cancelledNodes}}, and the next monitor run 
({{processCancelledNodes}}, every 
{{ozone.scm.datanode.admin.monitor.interval}}, default 30s) puts the node back 
to IN_SERVICE and stops tracking it.

Until that run the node still reads DECOMMISSIONING, ENTERING_MAINTENANCE or 
IN_MAINTENANCE. {{NodeDecommissionManager.continueAdminForNode}} decides from 
that op state alone, and for those three states it calls 
{{DatanodeAdminMonitor.startMonitoring}}, which first removes the node from 
{{cancelledNodes}}. The queued recommission is dropped, and the node is put 
back into the admin workflow with its admin state unchanged. The recommission 
RPC had already returned an empty error list.

h3. Trigger
# {{ozone admin datanode decommission <dn>}} (or {{maintenance}}), and the 
monitor runs, so the node is tracked.
# {{ozone admin datanode recommission <dn>}}, which returns no error.
# Within the same monitor interval, one of the paths that resume the admin 
workflow runs for that node:
#* the datanode re-registers with a changed IP address or hostname. 
{{SCMNodeManager.register}} fires NODE_ADDRESS_UPDATE and 
{{NodeAddressUpdateHandler}} calls {{continueAdminForNode}}.
#* this SCM steps down and is elected leader again. 
{{NodeDecommissionManager.onBecomeLeader}} calls {{continueAdminForNode}} for 
every node, and the monitor queue survives on the same process.
# The queued recommission is gone and the node is tracked again, still 
DECOMMISSIONING or in maintenance.

{{NewNodeHandler}} calls {{continueAdminForNode}} as well, for a newly 
registered node whose persisted state is not IN_SERVICE. A queued recommission 
for a node that is new to this SCM is unlikely (the queue is empty after a 
restart), and that path was not exercised.

h3. Impact
* The recommission is lost with no error anywhere: the CLI reports success, and 
nothing restores it, because the node is no longer queued and its op state was 
never changed.
* For a decommission, the workflow runs on to its end and the node becomes 
DECOMMISSIONED, which is terminal. This is the same for both triggers. An 
operator who cancelled a decommission sees it complete anyway, and has to 
notice and recommission again.
* For maintenance, the cancelled request proceeds as well: the node enters 
IN_MAINTENANCE and stays out of service until the end time given with the 
original request expires, or indefinitely if none was given (argued from the 
code, not run).
* No data is at risk in either case.

h3. Affected versions
{{continueAdminForNode}} and {{onBecomeLeader}} arrived with HDDS-5090, first 
released in 1.2.0, and the NODE_ADDRESS_UPDATE trigger with HDDS-5916 in 1.3.0. 
{{continueAdminForNode}}, {{recommission}} and the {{cancelledNodes}} removal 
in {{startMonitoring}} are unchanged in every release since, up to and 
including 2.2.1, and on master at 20d95ed37fe.

h3. Reproduction
PASS on the unmodified source, deterministic (two identical runs). 
[^TestBugCR12ResumedAdminDropsQueuedRecommission.java] 
({{hadoop-hdds/server-scm}}) runs one real SCM per test with its own 
decommission manager and event queue, driven through 
{{SCMNodeManager.register}} and {{processHeartbeat}}, the {{decommissionNodes}} 
and {{recommissionNodes}} methods that {{SCMClientProtocolServer}} calls, the 
real NODE_ADDRESS_UPDATE handler and {{DatanodeAdminMonitorImpl.run}}. The 
scheduled monitor is set to 1h and each tick is run by hand in place of the 30s 
interval. The simulated datanode changes its persisted state only when SCM 
sends {{SetNodeOperationalStateCommand}}, as 
{{SetNodeOperationalStateCommandHandler}} does. Output on the unmodified source:
{noformat}
recommission (queued): state=DECOMMISSIONING tracked=true queuedRecommissions=1 
pending=0
address change: state=DECOMMISSIONING tracked=true queuedRecommissions=0 
pending=1
monitor run after the address change: state=DECOMMISSIONED tracked=false 
queuedRecommissions=0 pending=0
recommission (queued): state=DECOMMISSIONING tracked=true queuedRecommissions=1 
pending=0
leader change: state=DECOMMISSIONING tracked=true queuedRecommissions=0 
pending=1
monitor run after the leader change: state=DECOMMISSIONED tracked=false 
queuedRecommissions=0 pending=0
{noformat}
A control without a resume in that window ends IN_SERVICE and untracked. A 
passing test means the defect is present. With the patch the two defect tests 
fail on the queued recommission count right after the resume ({{expected: 0 but 
was: 1}}), and the control passes. Not run on a cluster with real datanodes.

h3. Patch
[^CR-12-keep-queued-recommission-when-resuming-admin.patch], against 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
20d95ed37fe.

{{DatanodeAdminMonitor}} gets {{isStopMonitoringQueued}}, which reports under 
the monitor lock whether a request queued by {{stopMonitoring}} is still 
waiting, and {{continueAdminForNode}} returns early when it is. A node with a 
queued recommission is one the monitor still knows about and is about to put 
back IN_SERVICE, so there is nothing to resume for it. The queue check and the 
application of the recommission both run under the monitor lock 
({{isStopMonitoringQueued}} is synchronized, {{processCancelledNodes}} runs 
inside the synchronized block of {{run()}}), so the check cannot find an empty 
queue while the op state is still the admin state; and if the monitor applies 
the recommission between the check and the status read, that read returns 
IN_SERVICE and nothing is tracked again. No op state is written on this path, 
{{recommission}}, {{startMonitoring}} and {{stopMonitoring}} are unchanged, and 
a node with no queued recommission is tracked again exactly as before.

Op state and the monitor queues live in the leader SCM's memory and are not in 
the Ratis log, so a real failover to another SCM process loses a queued 
recommission before and after this patch. The case this patch covers is a step 
down and re-election of the same process, where the queue survives.

HDDS-17220 fixes the other consumer of the same queued state 
({{startDecommission}} and {{startMaintenance}} take a "Nothing to do" branch 
while a recommission is queued). Both fixes are needed and they compose: that 
patch withdraws the queued recommission before it reads the node status, after 
which the guard added here is a no op, and its re-queue on a rejected request 
is honoured by the guard. The two patches insert their new monitor method at 
the same place in {{DatanodeAdminMonitor}} and {{DatanodeAdminMonitorImpl}}, so 
whichever lands second needs that one hunk moved; keeping both methods is the 
whole resolution.

Covered by {{testQueuedRecommissionSurvivesContinueAdmin}} in the existing 
{{TestNodeDecommissionManager}}: a decommissioning, an entering maintenance and 
an in maintenance node are recommissioned, both resume entry points are called 
before the next monitor run, and after that run all three have to be IN_SERVICE 
and untracked while a fourth node that is DECOMMISSIONING with no queued 
recommission has to be tracked. Without the change it fails with {{expected: 
<IN_SERVICE> but was: <DECOMMISSIONING>}}. With it 
{{TestNodeDecommissionManager}}, {{TestDatanodeAdminMonitor}}, 
{{TestNodeDecommissionMetrics}}, {{TestSCMNodeManager}}, 
{{TestNodeStateManager}} and {{TestDeadNodeHandler}} (128 tests) pass, 
{{TestDecommissionAndMaintenance}} (9 tests, mini cluster) passes, 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