Siyao Meng created HDDS-17549:
---------------------------------

             Summary: Without security, an OM follower runs the preExecute of 
S3 writes before rejecting them, so a lagging follower can return an error, not 
NotLeader
                 Key: HDDS-17549
                 URL: https://issues.apache.org/jira/browse/HDDS-17549
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-4-check-leader-for-s3-writes-without-security.patch, 
TestBugCR4S3WriteOnFollowerSkipsLeaderCheck.java

h3. Mechanism
{{OzoneManagerProtocolServerSideTranslatorPB.internalProcessRequest}} calls 
{{OzoneManagerRatisUtils.checkLeaderStatus}} for a write only when the request 
has no {{S3Authentication}}, with the comment that the leader status was 
already verified while validating the credentials. That holds only when 
security is enabled: {{S3SecurityUtil.validateS3Credential}} checks the leader 
on both of its paths (the STS path directly, the other one through 
{{OzoneDelegationTokenSecretManager.retrievePassword}}), but it does nothing at 
all when security is disabled. The S3 Gateway sets {{S3Authentication}} on 
every request ({{EndpointBase.initialization}}), so on a cluster without 
security a write from the S3 Gateway that reaches a follower goes straight to 
{{OMExecutionFlow.submit}}. The follower creates the client request and runs 
its {{preExecute}} against its own state, and only the Ratis submit that 
follows rejects it as not leader.

HDDS-5885 skipped the second check because the translator itself then called 
{{checkLeaderStatus}} for every request with {{S3Authentication}}. HDDS-6686 
moved that check into {{retrievePassword}}, which only runs with security 
enabled, and left the skip in place.

h3. Trigger
A cluster without security, OM HA, and an S3 Gateway write that is the first 
call of the S3 Gateway's OM client to reach a follower. Most S3 requests do not 
get there: every S3 Gateway write endpoint first sends {{GetS3VolumeContext}} 
and {{InfoBucket}} through the same OM proxy, and with the default 
configuration (no follower reads) a follower rejects those reads with 
NotLeader, so the client is redirected before it writes. A write reaches a 
follower first when the leader changes between those reads and the write of one 
S3 request, or when an upload spans a leader change ({{AllocateBlock}} and 
{{CommitKey}} are sent mid stream with no read before them). Both the default 
Hadoop RPC transport and the gRPC transport reach the same translator method. 
Secure clusters are not affected.

h3. Impact
* If {{preExecute}} succeeds, Ratis on the follower rejects the request with 
NotLeader, the client fails over and the leader runs the request again. The 
cost is the wasted work on the follower, and for {{CreateKey}} (with data size) 
and {{AllocateBlock}} a block ID allocated in SCM that is never written. SCM 
keeps no state for it ({{BlockManagerImpl.newBlock}} only takes a local ID from 
the sequence generator in an open container and bumps a pipeline metric, and 
container usage comes from datanode reports), so nothing needs cleaning up; the 
cost is the wasted call.
* If {{preExecute}} fails because the follower has not applied recent changes 
yet, the follower returns an ordinary error response, not NotLeader, so the 
client does not retry on another OM and the error reaches the S3 client. In the 
reproduction a {{CreateKey}} for a bucket the leader had already created 
returned {{BUCKET_NOT_FOUND}}. Through the S3 Gateway this needs both a write 
that reaches the follower first (see Trigger) and a bucket created moments 
before, which the follower has not applied yet. Other write requests whose 
{{preExecute}} looks up the volume or bucket (for example {{AllocateBlock}}, or 
bucket link resolution in {{resolveBucketAndCheckKeyAcls}}) can fail the same 
way (argued, not run).
* No data loss and no state divergence: {{preExecute}} does not write to the OM 
DB, and the request is never applied unless it is submitted through the leader. 
Without security the request identity is not authenticated anyway, so no access 
check is weakened.

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugCR4S3WriteOnFollowerSkipsLeaderCheck.java] 
({{hadoop-ozone/integration-test}}) starts a mini cluster without security, 
with three OMs and one datanode, and hands {{CreateKey}} requests to a 
follower's {{OzoneManagerProtocolServerSideTranslatorPB}}, the method both RPC 
servers call. It shows, on the same follower:
# a {{CreateKey}} without {{S3Authentication}} is rejected with 
{{OMNotLeaderException}} and the SCM block allocation count does not change;
# the same request with {{S3Authentication}} also ends in 
{{OMNotLeaderException}}, but only after the SCM block allocation count went up;
# with the follower's apply of one {{CreateBucket}} held (its state machine 
handler is wrapped for this, the only step that is not a request), a 
{{CreateKey}} with {{S3Authentication}} for that bucket returns 
{{BUCKET_NOT_FOUND}} although the leader has the bucket.

A passing test means the defect is present. The S3 Gateway itself and the 
client's retry were not run; the client side behaviour above follows from the 
response types.

h3. Patch
[^CR-4-check-leader-for-s3-writes-without-security.patch], against 
efe0c7eba1527530d1001f6517b733576fdc6d61 (current master).

The translator now skips its own leader check only when the request has 
{{S3Authentication}} and security is enabled, which is exactly when 
{{validateS3Credential}} has done the check. Secure clusters still check once, 
and reads are unchanged, so follower reads for S3 requests on clusters without 
security keep working. The simpler alternative is to drop the skip and always 
check the leader for writes, at the cost of a second check on secure S3 writes.

The fix uses {{isSecurityEnabled()}} as the signal that the leader was already 
checked. Related: HDDS-15617 has the same root ({{validateS3Credential}} does 
nothing without security) and proposes validating S3 signatures on clusters 
without security; if that is done through {{retrievePassword}}, it adds a 
leader check there, and this condition should be revisited with it (it would 
also force S3 reads on such clusters to the leader).

Covered by two new parameterized tests in {{TestS3DerivedKey}}, without 
security and with security for both an STS request and an ordinary S3 secret: a 
write on a follower is rejected with NotLeader before the retry cache lookup 
and the submit, and on the leader the leader is checked exactly once. For the 
ordinary secret the token manager is a mock that checks the leader first, as 
{{retrievePassword}} does. Without the change both tests fail in the cases 
without security; a variant that always checks fails the "exactly once" test in 
both secure cases. With the patch the second step of the reproduction fails, 
because the follower rejects the request before allocating a block.

Found by code review of the OM follower read and read consistency routing 
paths, as part of the TLA+ verification effort under HDDS-15926, on commit 
efe0c7eba1527530d1001f6517b733576fdc6d61. 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