Siyao Meng created HDDS-17220:
---------------------------------

             Summary: Decommission or maintenance requested while a 
recommission is still queued is acknowledged and then lost, and the datanode 
returns to IN_SERVICE
                 Key: HDDS-17220
                 URL: https://issues.apache.org/jira/browse/HDDS-17220
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-2-withdraw-queued-recommission-on-new-admin-request.patch, 
TestBugCR2QueuedRecommissionDropsAdminRequest.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 is still DECOMMISSIONING, DECOMMISSIONED, 
ENTERING_MAINTENANCE or IN_MAINTENANCE. {{startDecommission}} and 
{{startMaintenance}} look only at the op state, so a new request for the same 
kind of state takes the "Nothing to do" branch: no error is returned and 
{{monitor.startMonitoring}}, which removes a queued cancel, is not called. The 
queued recommission then wins.

h3. Trigger
# {{ozone admin datanode decommission <dn>}} (or {{maintenance}}), and the 
monitor runs.
# {{ozone admin datanode recommission <dn>}}.
# Within the same monitor interval, {{ozone admin datanode decommission <dn>}} 
again (with or without {{--force}}), or {{maintenance}}.
# The next monitor run puts the node IN_SERVICE and stops tracking it.

The same happens for a DECOMMISSIONED node and when the recommission is queued 
before the monitor has picked up the node. A recommission followed by a request 
of the other kind (for example maintenance, recommission, decommission) is not 
lost silently: it is rejected with "Cannot decommission node ... in state 
ENTERING_MAINTENANCE", and the node returns to IN_SERVICE.

h3. Impact
* The request returns an empty error list, so the CLI reports success, but the 
node ends IN_SERVICE and untracked. No later monitor run, heartbeat or leader 
change brings it back, because it is now IN_SERVICE.
* An operator who then stops the node believing it is in maintenance gets a 
normal dead node, and SCM re-replicates its containers (argued from code, not 
run). No data is at risk.

h3. Reproduction
PASS on the unmodified source, deterministic. 
[^TestBugCR2QueuedRecommissionDropsAdminRequest.java] 
({{hadoop-hdds/server-scm}}) uses the setup of {{TestNodeDecommissionManager}}: 
a real {{SCMNodeManager}} with registered datanodes and a real 
{{NodeDecommissionManager}} (with a mocked {{ReplicationManager}}, so it also 
runs on master), called through the {{decommissionNodes}}, 
{{startMaintenanceNodes}} and {{recommissionNodes}} methods that 
{{SCMClientProtocolServer}} calls. The scheduled monitor is set to 1h and the 
monitor is run by hand in place of the 30s interval. Output on the pin:
{noformat}
monitor run after decommission: state=DECOMMISSIONING tracked=true
recommission (queued): state=DECOMMISSIONING tracked=true
decommission again, errors=0: state=DECOMMISSIONING tracked=true
monitor run after the new request: state=IN_SERVICE tracked=false
monitor run after maintenance: state=ENTERING_MAINTENANCE tracked=true
recommission (queued): state=ENTERING_MAINTENANCE tracked=true
maintenance again, errors=0: state=ENTERING_MAINTENANCE tracked=true
monitor run after the new request: state=IN_SERVICE tracked=false
{noformat}
A control with a monitor run between the recommission and the new request ends 
ENTERING_MAINTENANCE and tracked. A passing test means the defect is present. 
With the patch the two defect tests fail, because the node stays 
DECOMMISSIONING or ENTERING_MAINTENANCE and tracked, and the control passes. 
Not run on a cluster with real datanodes.

h3. Patch
[^CR-2-withdraw-queued-recommission-on-new-admin-request.patch], against 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
20d95ed37fe.

{{DatanodeAdminMonitor}} gets {{withdrawStopMonitoring}}, which removes every 
recommission queued for the node under the monitor lock and reports whether 
there was one. More than one can be queued, because the host list of a command 
is not deduplicated and because two commands can arrive in one monitor 
interval. {{startDecommission}} and {{startMaintenance}} call it before they 
read the node status. If a recommission was withdrawn and the node is already 
in the requested kind of admin state, the node keeps its state, which is not 
written, and {{startMonitoring}} is called again unless the node is 
DECOMMISSIONED (that node is not tracked, and keeping it DECOMMISSIONED is 
enough). Keeping the state also keeps a stopped IN_MAINTENANCE node in 
maintenance. A request of the other kind is still rejected, and the 
recommission is queued again, so its outcome does not change. Requests without 
a queued recommission are unchanged.

Op state and the monitor queues live in the leader SCM's memory and are not in 
the Ratis log, so a failover while the recommission is queued loses the 
recommission, before and after this patch.

Covered by {{testAdminRequestReplacesQueuedRecommission}} in the existing 
{{TestNodeDecommissionManager}}: decommissioning, entering maintenance and dead 
IN_MAINTENANCE nodes keep their state and stay tracked, a node whose 
recommission was queued before the monitor picked it up is tracked again, a 
DECOMMISSIONED node stays DECOMMISSIONED and untracked whether its recommission 
was queued once or twice, and a decommission of a node in maintenance is 
rejected and the node returns to IN_SERVICE. Without the change it fails with 
{{expected: <DECOMMISSIONING> but was: <IN_SERVICE>}}. With it 
{{TestNodeDecommissionManager}}, {{TestDatanodeAdminMonitor}}, 
{{TestNodeDecommissionMetrics}} and {{TestSCMNodeManager}} (113 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