Return Atmos objectID as ETag

Previously Atmos returned null.  Also rework the fix for JCLOUDS-339
which does not reproduce with AT&T Synaptic.  Fixes gaul/s3proxy#247.
This commit is contained in:
Andrew Gaul 2017-11-11 11:08:56 -08:00
parent e473d7df6a
commit 50026c8f2d
8 changed files with 48 additions and 50 deletions

View File

@ -45,7 +45,6 @@ import org.jclouds.atmos.fallbacks.TrueOn404FalseOnPathNotEmpty;
import org.jclouds.atmos.filters.SignRequest;
import org.jclouds.atmos.functions.AtmosObjectName;
import org.jclouds.atmos.functions.ParseDirectoryListFromContentAndHeaders;
import org.jclouds.atmos.functions.ParseNullableURIFromListOrLocationHeaderIf20x;
import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ParseUserMetadataFromHeaders;
@ -55,6 +54,7 @@ import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.BlobStoreFallbacks.NullOnKeyAlreadyExists;
import org.jclouds.blobstore.BlobStoreFallbacks.ThrowContainerNotFoundOn404;
import org.jclouds.blobstore.BlobStoreFallbacks.ThrowKeyNotFoundOn404;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.options.GetOptions;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.rest.annotations.BinderParam;
@ -105,7 +105,7 @@ public interface AtmosClient extends Closeable {
@POST
@Path("/{parent}/{name}")
@Headers(keys = EXPECT, values = "100-continue")
@ResponseParser(ParseNullableURIFromListOrLocationHeaderIf20x.class)
@ResponseParser(ParseURIFromListOrLocationHeaderIf20x.class)
@Consumes(MediaType.WILDCARD)
URI createFile(@PathParam("parent") String parent, @PathParam("name") @ParamParser(AtmosObjectName.class)
@BinderParam(BindMetadataToHeaders.class) AtmosObject object, PutOptions... options);

View File

@ -59,7 +59,7 @@ public class DirectoryEntryListToResourceMetadataList implements
.get(), null, null, null, null, ImmutableMap.<String, String>of());
else {
BlobMetadataImpl metadata = new BlobMetadataImpl(from.getObjectID(), from.getObjectName(), defaultLocation.get(),
null, null, null, from.getModifiedTime(), ImmutableMap.<String, String>of(), null,
null, from.getObjectID(), null, from.getModifiedTime(), ImmutableMap.<String, String>of(), null,
null, new BaseMutableContentMetadata());
MutableBlobMetadataImpl mutable = new MutableBlobMetadataImpl(metadata);
mutable.setSize(from.getSize());

View File

@ -80,6 +80,7 @@ public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMe
to.setUserMetadata(lowerKeyMetadata);
to.setSize(from.getContentMetadata().getContentLength());
to.setTier(Tier.STANDARD);
to.setETag(from.getSystemMetadata().getObjectID());
return to;
}
}

View File

@ -1,40 +0,0 @@
/*
* 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.jclouds.atmos.functions;
import static org.jclouds.http.HttpUtils.releasePayload;
import java.net.URI;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
/**
* Parses a single URI from a list, returning null when blob length was zero.
* Atmos returns "HTTP/1.1 201 null" when putting zero-length blobs.
*/
public class ParseNullableURIFromListOrLocationHeaderIf20x extends ParseURIFromListOrLocationHeaderIf20x {
@Override
public URI apply(HttpResponse from) {
if (from.getStatusCode() == 201 && request.getPayload().getContentMetadata().getContentLength() == 0) {
releasePayload(from);
return null;
}
return super.apply(from);
}
}

View File

@ -20,6 +20,7 @@ import static org.jclouds.util.Predicates2.retry;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import javax.inject.Inject;
import javax.inject.Provider;
@ -73,13 +74,19 @@ public class AtmosUtils {
final String path = container + "/" + blob.getMetadata().getName();
final AtmosObject object = blob2Object.apply(blob);
URI uri;
try {
sync.createFile(container, object, options);
uri = sync.createFile(container, object, options);
} catch (KeyAlreadyExistsException e) {
deletePathAndEnsureGone(sync, path);
sync.createFile(container, object, options);
uri = sync.createFile(container, object, options);
}
return path;
// return object ID as the ETag
String objectId = uri.getPath();
String prefix = "/rest/objects/";
checkState(objectId.startsWith(prefix), objectId);
return objectId.substring(prefix.length());
}
public static void deletePathAndEnsureGone(final AtmosClient sync, String path) {

View File

@ -30,7 +30,6 @@ import org.jclouds.atmos.domain.AtmosObject;
import org.jclouds.atmos.fallbacks.TrueOn404FalseOnPathNotEmpty;
import org.jclouds.atmos.filters.SignRequest;
import org.jclouds.atmos.functions.ParseDirectoryListFromContentAndHeaders;
import org.jclouds.atmos.functions.ParseNullableURIFromListOrLocationHeaderIf20x;
import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead;
@ -164,7 +163,7 @@ public class AtmosClientTest extends BaseRestAnnotationProcessingTest<AtmosClien
assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT + ": */*\nExpect: 100-continue\n");
assertPayloadEquals(request, "hello", "text/plain", false);
assertResponseParserClassEquals(method, request, ParseNullableURIFromListOrLocationHeaderIf20x.class);
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertFallbackClassEquals(method, null);
@ -182,7 +181,7 @@ public class AtmosClientTest extends BaseRestAnnotationProcessingTest<AtmosClien
+ ": */*\nExpect: 100-continue\nx-emc-groupacl: other=READ\nx-emc-useracl: root=FULL_CONTROL\n");
assertPayloadEquals(request, "hello", "text/plain", false);
assertResponseParserClassEquals(method, request, ParseNullableURIFromListOrLocationHeaderIf20x.class);
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertFallbackClassEquals(method, null);

View File

@ -23,14 +23,19 @@ import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.domain.Tier;
import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.testng.SkipException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.google.common.io.ByteSource;
@Test(groups = { "integration", "live" })
public class AtmosIntegrationLiveTest extends BaseBlobIntegrationTest {
public AtmosIntegrationLiveTest() {
@ -197,4 +202,30 @@ public class AtmosIntegrationLiveTest extends BaseBlobIntegrationTest {
// Atmos maps all tiers to STANDARD
assertThat(metadata.getTier()).isEqualTo(Tier.STANDARD);
}
// TODO: promote test to portable abstraction?
@Test(groups = { "integration", "live" })
public void testETag() throws Exception {
String blobName = "test-etag";
ByteSource payload = ByteSource.empty();
BlobStore blobStore = view.getBlobStore();
String containerName = getContainerName();
try {
Blob blob = blobStore.blobBuilder(blobName)
.payload(payload)
.contentLength(payload.size())
.build();
String eTag = blobStore.putBlob(containerName, blob);
assertThat(eTag).hasSize(44);
BlobMetadata metadata = blobStore.blobMetadata(containerName, blobName);
assertThat(metadata.getETag()).isEqualTo(eTag);
for (StorageMetadata sm : blobStore.list(containerName, ListContainerOptions.NONE)) {
assertThat(sm.getETag()).isEqualTo(eTag);
}
} finally {
returnContainer(containerName);
}
}
}

View File

@ -71,7 +71,7 @@ public class ParseURIFromListOrLocationHeaderIf20x implements Function<HttpRespo
locationUri = URI.create(location);
return Uris.uriBuilder(request.getEndpoint()).path(locationUri.getPath()).query(locationUri.getQuery()).build();
} else {
throw new HttpResponseException("no uri in headers or content", null, from);
return null;
}
}