Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalLong;
import org.apache.hadoop.hdds.client.ContainerBlockID;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.scm.OzoneClientConfig;
import org.apache.hadoop.hdds.scm.XceiverClientFactory;
import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.hdds.scm.storage.StreamBuffer;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
Expand Down Expand Up @@ -255,7 +257,10 @@ void commitKey(long offset) throws IOException {
commitUploadPartInfo =
omClient.commitMultipartUploadPart(buildKeyArgs(), openID);
} else {
omClient.commitKey(buildKeyArgs(), openID);
final OptionalLong modificationTime = omClient.commitKeyWithModificationTime(buildKeyArgs(), openID);
if (modificationTime.isPresent()) {
metadata.put(OzoneConsts.MODIFICATION_TIME, String.valueOf(modificationTime.getAsLong()));
}
}
} else {
LOG.warn("Closing KeyDataStreamOutput, but key args is null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
import org.apache.hadoop.hdds.client.ContainerBlockID;
Expand All @@ -39,6 +40,7 @@
import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.hdds.scm.storage.BufferPool;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
Expand Down Expand Up @@ -330,7 +332,10 @@ void commitKey(long offset) throws IOException {
commitUploadPartInfo =
omClient.commitMultipartUploadPart(buildKeyArgs(), openID);
} else {
omClient.commitKey(buildKeyArgs(), openID);
final OptionalLong modificationTime = omClient.commitKeyWithModificationTime(buildKeyArgs(), openID);
if (modificationTime.isPresent()) {
metadata.put(OzoneConsts.MODIFICATION_TIME, String.valueOf(modificationTime.getAsLong()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata is to persist in OM DB, a different goal. Please add a new field for LastModified value.

}
}
} else {
LOG.warn("Closing KeyOutputStream, but key args is null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.apache.hadoop.ozone.om.helpers;

import java.util.OptionalLong;

/**
* This class holds information about the response from commit multipart
* upload part request.
Expand All @@ -27,9 +29,16 @@ public class OmMultipartCommitUploadPartInfo {

private final String eTag;

private final Long modificationTime;

public OmMultipartCommitUploadPartInfo(String partName, String eTag) {
this(partName, eTag, null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this constructor if it's not used anymore.

}

public OmMultipartCommitUploadPartInfo(String partName, String eTag, Long modificationTime) {
this.partName = partName;
this.eTag = eTag;
this.modificationTime = modificationTime;
}

public String getETag() {
Expand All @@ -39,4 +48,8 @@ public String getETag() {
public String getPartName() {
return partName;
}

public OptionalLong getModificationTime() {
return modificationTime == null ? OptionalLong.empty() : OptionalLong.of(modificationTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.OptionalLong;
import java.util.UUID;
import org.apache.hadoop.fs.SafeModeAction;
import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
Expand Down Expand Up @@ -252,6 +253,17 @@ default void commitKey(OmKeyArgs args, long clientID)
"this to be implemented, as write requests use a new approach.");
}

/**
* Commit a key and optionally return the stored modification time (epoch millis).
* <p>
* This is backward compatible with older OM versions which do not return
* the value in {@code CommitKeyResponse}.
*/
default OptionalLong commitKeyWithModificationTime(OmKeyArgs args, long clientID) throws IOException {

@ChenSammi ChenSammi Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that LastModified element is a mandatory part of CopyObject and UploadPartCopy.

https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html

So without this patch, Ozone now returns incorrect LastModified? Since there is already lastModified field in CopyObjectResponse and CopyPartResult.

Since it's mandatory for S3, we don't need to maintain both commitKeyWithModificationTime and commitKey, update commitKey to return long, instead of OptionalLong, making sure lastModified is returned after a successful commit, so we can get a clean logic and code.

@fmorg-git fmorg-git Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without this patch, Ozone now returns incorrect LastModified?

There were two cases previously per my understanding:

  1. CopyObject: the current time was set when the key is committed. Then an extra lookup is performed in getClientProtocol().getKeyDetails() to find what that modification time is (so it was correct). This patch changes to return the modification time in the CommitKeyResponse itself so the extra lookup isn't needed (and this extra lookup was breaking STS).
  2. UploadPartCopy: it was just doing an Instant.now() in the CopyPartResult constructor, so the returned LastModified was the gateway's wall‑clock at response time, not the OM‑side commit time of the part key, so it was not correct but was approximate

For both these cases, the patch now returns the actual lastModified that was set at the time the key was committed in the response itself so that a) no extra fetch is needed and b) it is more accurate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's mandatory for S3, we don't need to maintain both commitKeyWithModificationTime and commitKey, update commitKey to return long,

@ChenSammi - would this be a breaking api change to commitKey for older clients or mixed client versions?

commitKey(args, clientID);
return OptionalLong.empty();
}

/**
* Synchronize the key length. This will make the change from the client
* visible. The client is identified by the clientID.
Expand Down Expand Up @@ -1146,7 +1158,6 @@ default CancelPrepareResponse cancelOzoneManagerPrepare() throws IOException {
EchoRPCResponse echoRPCReq(byte[] payloadReq, int payloadSizeResp,
boolean writeToRatis) throws IOException;


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be reverted.

/**
* Start the lease recovery of a file.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -828,6 +829,11 @@ public void commitKey(OmKeyArgs args, long clientId)
updateKey(args, clientId, false, false);
}

@Override
public OptionalLong commitKeyWithModificationTime(OmKeyArgs args, long clientId) throws IOException {
return updateKeyAndGetModificationTime(args, clientId, false, false);
}

@Override
public void recoverKey(OmKeyArgs args, long clientId)
throws IOException {
Expand All @@ -849,6 +855,12 @@ public static void setReplicationConfig(ReplicationConfig replication,

private void updateKey(OmKeyArgs args, long clientId, boolean hsync, boolean recovery)
throws IOException {
// Preserve legacy behavior (ignore response payload).
updateKeyAndGetModificationTime(args, clientId, hsync, recovery);
}

private OptionalLong updateKeyAndGetModificationTime(OmKeyArgs args, long clientId, boolean hsync, boolean recovery)
throws IOException {
CommitKeyRequest.Builder req = CommitKeyRequest.newBuilder();
List<OmKeyLocationInfo> locationInfoList = args.getLocationInfoList();
Objects.requireNonNull(locationInfoList, "locationInfoList == null");
Expand All @@ -874,7 +886,11 @@ private void updateKey(OmKeyArgs args, long clientId, boolean hsync, boolean rec
.setCommitKeyRequest(req)
.build();

handleError(submitRequest(omRequest));
final OMResponse resp = handleError(submitRequest(omRequest));
if (resp.hasCommitKeyResponse() && resp.getCommitKeyResponse().hasModificationTime()) {
return OptionalLong.of(resp.getCommitKeyResponse().getModificationTime());
}
return OptionalLong.empty();
}

@Override
Expand Down Expand Up @@ -1791,7 +1807,8 @@ public OmMultipartCommitUploadPartInfo commitMultipartUploadPart(
handleError(submitRequest(omRequest))
.getCommitMultiPartUploadResponse();

return new OmMultipartCommitUploadPartInfo(response.getPartName(), response.getETag());
final Long modificationTime = response.hasModificationTime() ? response.getModificationTime() : null;
return new OmMultipartCommitUploadPartInfo(response.getPartName(), response.getETag(), modificationTime);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,8 @@ message CommitKeyRequest {

message CommitKeyResponse {

// Modification time of the committed key, set by OM during preExecute.
optional uint64 modificationTime = 1;
}

message AllocateBlockRequest {
Expand Down Expand Up @@ -1790,6 +1792,8 @@ message MultipartCommitUploadPartResponse {
optional string partName = 1;
// This one is returned as Etag for S3.
optional string eTag = 2;
// Modification time of the committed part key, set by OM during preExecute.
optional uint64 modificationTime = 3;
}

message MultipartUploadCompleteRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.apache.hadoop.ozone.om.response.key.OMKeyCommitResponse;
import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CommitKeyRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CommitKeyResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyLocation;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
Expand Down Expand Up @@ -409,6 +410,10 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, Execut

omBucketInfo.incrUsedBytes(correctedSpace);

omResponse.setCommitKeyResponse(CommitKeyResponse.newBuilder()
.setModificationTime(commitKeyArgs.getModificationTime())
.build());

omClientResponse = new OMKeyCommitResponse(omResponse.build(),
omKeyInfo, dbOzoneKey, dbOpenKey, omBucketInfo.copyObject(),
oldKeyVersionsToDeleteMap, isHSync, newOpenKeyInfo, dbOpenKeyToDeleteKey, openKeyToDelete);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.apache.hadoop.ozone.om.response.OMClientResponse;
import org.apache.hadoop.ozone.om.response.key.OMKeyCommitResponseWithFSO;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CommitKeyRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CommitKeyResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
Expand Down Expand Up @@ -347,6 +348,10 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, Execut

omBucketInfo.incrUsedBytes(correctedSpace);

omResponse.setCommitKeyResponse(CommitKeyResponse.newBuilder()
.setModificationTime(commitKeyArgs.getModificationTime())
.build());

omClientResponse = new OMKeyCommitResponseWithFSO(omResponse.build(),
omKeyInfo, dbFileKey, dbOpenFileKey, omBucketInfo.copyObject(),
oldKeyVersionsToDeleteMap, volumeId, isHSync, newOpenKeyInfo, dbOpenKeyToDeleteKey, openKeyToDelete);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, Execut
if (eTag != null) {
commitResponseBuilder.setETag(eTag);
}
commitResponseBuilder.setModificationTime(keyArgs.getModificationTime());
omResponse.setCommitMultiPartUploadResponse(commitResponseBuilder);
omClientResponse =
getOmClientResponse(ozoneManager, keyVersionsToDeleteMap, openKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ public CopyPartResult() {
}

public CopyPartResult(String eTag) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this constructor if it's not used anymore.

this(eTag, Instant.now());
}

public CopyPartResult(String eTag, Instant lastModified) {
this.eTag = eTag;
this.lastModified = Instant.now();
this.lastModified = lastModified;
}

public Instant getLastModified() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hadoop.ozone.s3.endpoint;

/**
* Result of a copy operation.
*/
public class CopyResult {
private final String eTag;
private final long size;
private final long modificationTime;

public CopyResult(String eTag, long size, long modificationTime) {
this.eTag = eTag;
this.size = size;
this.modificationTime = modificationTime;
}

public String getETag() {
return eTag;
}

public long getSize() {
return size;
}

public long getModificationTime() {
return modificationTime;
}
}
Loading