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 @@ -614,7 +614,6 @@ public Response head(
key = getClientProtocol().headS3Object(bucketName, keyPath);

isFile(keyPath, key);
// TODO: return the specified range bytes of this object.
} catch (OMException ex) {
auditReadFailure(s3GAction, ex);
getMetrics().updateHeadKeyFailureStats(startNanos);
Expand All @@ -637,10 +636,38 @@ public Response head(
S3StorageType.STANDARD :
S3StorageType.fromReplicationConfig(key.getReplicationConfig());

ResponseBuilder response = Response.ok().status(HttpStatus.SC_OK)
.header(HttpHeaders.CONTENT_LENGTH, key.getDataSize())
.header(HttpHeaders.CONTENT_TYPE, "binary/octet-stream")
.header(STORAGE_CLASS_HEADER, s3StorageType.toString());
long length = key.getDataSize();
String rangeHeaderVal = getHeaders() != null ?
getHeaders().getHeaderString(RANGE_HEADER) : null;

RangeHeader rangeHeader = null;
if (rangeHeaderVal != null) {
rangeHeader = RangeHeaderParserUtil.parseRangeHeader(rangeHeaderVal, length);
if (rangeHeader.isInValidRange()) {
throw newError(S3ErrorTable.INVALID_RANGE, rangeHeaderVal);
}
}

ResponseBuilder response;

if (rangeHeaderVal == null || rangeHeader.isReadFull()) {
response = Response.ok().status(HttpStatus.SC_OK)
.header(HttpHeaders.CONTENT_LENGTH, length);
} else {
long startOffset = rangeHeader.getStartOffset();
long endOffset = rangeHeader.getEndOffset();
long contentLength = endOffset - startOffset + 1;
String contentRangeVal = RANGE_HEADER_SUPPORTED_UNIT + " " +
startOffset + "-" + endOffset + "/" + length;

response = Response.status(Status.PARTIAL_CONTENT)
.header(HttpHeaders.CONTENT_LENGTH, contentLength)
.header(CONTENT_RANGE_HEADER, contentRangeVal);
}

response.header(HttpHeaders.CONTENT_TYPE, "binary/octet-stream")
.header(STORAGE_CLASS_HEADER, s3StorageType.toString())
.header(ACCEPT_RANGE_HEADER, RANGE_HEADER_SUPPORTED_UNIT);

String eTag = key.getMetadata().get(OzoneConsts.ETAG);
if (eTag != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,73 +20,104 @@
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_FSO_DIRECTORY_CREATION_ENABLED;
import static org.apache.hadoop.ozone.s3.util.S3Consts.ACCEPT_RANGE_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.CONTENT_RANGE_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_HEADER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.client.ReplicationFactor;
import org.apache.hadoop.hdds.client.ReplicationType;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.ozone.client.OzoneBucket;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Test head object.
*/
public class TestObjectHead {
private static final String TEST_KEY = "key1";
private static final String TEST_VALUE = "0123456789";

private String bucketName = "b1";
private ObjectEndpoint keyEndpoint;
private OzoneBucket bucket;
private HttpHeaders headers;

@BeforeEach
public void setup() throws IOException {
//Create client stub and object store stub.
OzoneClient clientStub = new OzoneClientStub();
OzoneClientStub client = new OzoneClientStub();
client.getObjectStore().createS3Bucket(bucketName);
bucket = client.getObjectStore().getS3Bucket(bucketName);

// Create volume and bucket
clientStub.getObjectStore().createS3Bucket(bucketName);

bucket = clientStub.getObjectStore().getS3Bucket(bucketName);
try (OzoneOutputStream out = bucket.createKey(TEST_KEY,
TEST_VALUE.getBytes(UTF_8).length,
ReplicationConfig.fromTypeAndFactor(ReplicationType.RATIS,
ReplicationFactor.ONE), new HashMap<>())) {
out.write(TEST_VALUE.getBytes(UTF_8));
}

// Create HeadBucket and setClient to OzoneClientStub
headers = mock(HttpHeaders.class);
when(headers.getHeaderString(RANGE_HEADER)).thenReturn(null);
keyEndpoint = EndpointBuilder.newObjectEndpointBuilder()
.setClient(clientStub)
.setClient(client)
.setHeaders(headers)
.build();
}

@AfterEach
public void cleanup() throws IOException {
if (bucket != null) {
try {
bucket.deleteKey(TEST_KEY);
} catch (Exception e) {
// Ignore errors during cleanup to ensure test isolation
}
}
}

@Test
public void testHeadObject() throws Exception {
//GIVEN
String value = RandomStringUtils.secure().nextAlphanumeric(32);
OzoneOutputStream out = bucket.createKey("key1",
String testKey = "testKey";
try (OzoneOutputStream out = bucket.createKey(testKey,
value.getBytes(UTF_8).length,
ReplicationConfig.fromTypeAndFactor(ReplicationType.RATIS,
ReplicationFactor.ONE), new HashMap<>());
out.write(value.getBytes(UTF_8));
out.close();
ReplicationFactor.ONE), new HashMap<>())) {
out.write(value.getBytes(UTF_8));
}

//WHEN
Response response = keyEndpoint.head(bucketName, "key1");
Response response = keyEndpoint.head(bucketName, testKey);

//THEN
assertEquals(200, response.getStatus());
assertEquals(value.getBytes(UTF_8).length,
Long.parseLong(response.getHeaderString("Content-Length")));
Long.parseLong(response.getHeaderString(HttpHeaders.CONTENT_LENGTH)));

DateTimeFormatter.RFC_1123_DATE_TIME
.parse(response.getHeaderString("Last-Modified"));

bucket.deleteKey(testKey);
}

@Test
Expand Down Expand Up @@ -185,4 +216,54 @@ public void testHeadWhenKeyIsAFileAndKeyPathEndsWithASlash()
assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatus());
bucket.deleteKey(keyPath);
}

@Test
public void testHeadWithRangeHeader() throws Exception {
//GIVEN
when(headers.getHeaderString(RANGE_HEADER)).thenReturn("bytes=0-0");

//WHEN
Response response = keyEndpoint.head(bucketName, TEST_KEY);

//THEN
assertEquals(206, response.getStatus());
assertEquals("1", response.getHeaderString(HttpHeaders.CONTENT_LENGTH));
assertEquals(String.format("bytes 0-0/%d", TEST_VALUE.length()),
response.getHeaderString(CONTENT_RANGE_HEADER));
assertEquals("bytes", response.getHeaderString(ACCEPT_RANGE_HEADER));

// Test range from start to end of file
when(headers.getHeaderString(RANGE_HEADER)).thenReturn("bytes=0-");
response = keyEndpoint.head(bucketName, TEST_KEY);
assertEquals(206, response.getStatus());
assertEquals(String.valueOf(TEST_VALUE.length()),
response.getHeaderString(HttpHeaders.CONTENT_LENGTH));
assertEquals(String.format("bytes 0-%d/%d", TEST_VALUE.length() - 1, TEST_VALUE.length()),
response.getHeaderString(CONTENT_RANGE_HEADER));
}

@Test
public void testHeadWithInvalidRangeHeader() throws Exception {
// Invalid range: start (11) and end (10) exceed file length (10)
when(headers.getHeaderString(RANGE_HEADER)).thenReturn("bytes=11-10");

//WHEN/THEN
OS3Exception ex = assertThrows(OS3Exception.class,
() -> keyEndpoint.head(bucketName, TEST_KEY));
assertEquals(S3ErrorTable.INVALID_RANGE.getCode(), ex.getCode());
assertEquals(416, ex.getHttpCode());
}

@Test
public void testHeadWithoutRangeHeader() throws Exception {
//WHEN
Response response = keyEndpoint.head(bucketName, TEST_KEY);

//THEN
assertEquals(200, response.getStatus());
assertEquals(String.valueOf(TEST_VALUE.length()),
response.getHeaderString(HttpHeaders.CONTENT_LENGTH));
assertEquals("bytes", response.getHeaderString(ACCEPT_RANGE_HEADER));
assertNull(response.getHeaderString(CONTENT_RANGE_HEADER));
}
}
Loading