Updated Branches: refs/heads/master f46c86a97 -> f5e4012f9
JCLOUDS-306. refactored and completed swift ContainerApi Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/commit/f5e4012f Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/tree/f5e4012f Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/diff/f5e4012f Branch: refs/heads/master Commit: f5e4012f9e88b6cedfda5b61c760ef363ad7e772 Parents: f867518 Author: Adrian Cole <[email protected]> Authored: Fri Sep 27 21:38:00 2013 -0700 Committer: Adrian Cole <[email protected]> Committed: Sat Sep 28 07:12:01 2013 -0700 ---------------------------------------------------------------------- .../openstack/swift/v1/domain/Container.java | 52 +++- .../swift/v1/features/ContainerApi.java | 142 +++++++++- .../swift/v1/functions/FalseOnAccepted.java | 30 ++ .../v1/functions/ParseContainerFromHeaders.java | 46 +++ .../swift/v1/handlers/SwiftErrorHandler.java | 3 + .../swift/v1/options/ListContainersOptions.java | 77 ----- .../swift/v1/features/ContainerApiLiveTest.java | 90 +++++- .../swift/v1/features/ContainerApiMockTest.java | 280 +++++++++++++++++++ .../v1/options/ListContainersOptionsTest.java | 92 ------ .../swift/v1/parse/ParseContainerListTest.java | 56 ---- .../src/test/resources/container_list.json | 4 - 11 files changed, 623 insertions(+), 249 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java index 612aa80..cfee278 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java @@ -21,9 +21,14 @@ import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; import java.beans.ConstructorProperties; +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.openstack.swift.v1.features.ContainerApi; import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; /** * @see <a @@ -35,12 +40,14 @@ public class Container implements Comparable<Container> { private final String name; private final long objectCount; private final long bytesUsed; + private final Map<String, String> metadata; - @ConstructorProperties({ "name", "count", "bytes" }) - protected Container(String name, long objectCount, long bytesUsed) { + @ConstructorProperties({ "name", "count", "bytes", "metadata" }) + protected Container(String name, long objectCount, long bytesUsed, Map<String, String> metadata) { this.name = checkNotNull(name, "name"); this.objectCount = objectCount; this.bytesUsed = bytesUsed; + this.metadata = metadata == null ? ImmutableMap.<String, String> of() : metadata; } public String name() { @@ -55,6 +62,18 @@ public class Container implements Comparable<Container> { return bytesUsed; } + /** + * Empty except in {@link ContainerApi#get(String) GetContainer} commands. + * + * <h3>Note</h3> + * + * In current swift implementations, headers keys are lower-cased. This means + * characters such as turkish will probably not work out well. + */ + public Map<String, String> metadata() { + return metadata; + } + @Override public boolean equals(Object object) { if (this == object) { @@ -64,7 +83,8 @@ public class Container implements Comparable<Container> { final Container that = Container.class.cast(object); return equal(name(), that.name()) // && equal(objectCount(), that.objectCount()) // - && equal(bytesUsed(), that.bytesUsed()); + && equal(bytesUsed(), that.bytesUsed()) // + && equal(metadata(), that.metadata()); } else { return false; } @@ -72,7 +92,7 @@ public class Container implements Comparable<Container> { @Override public int hashCode() { - return Objects.hashCode(name(), objectCount(), bytesUsed()); + return Objects.hashCode(name(), objectCount(), bytesUsed(), metadata()); } @Override @@ -84,7 +104,8 @@ public class Container implements Comparable<Container> { return toStringHelper("") // .add("name", name()) // .add("objectCount", objectCount()) // - .add("bytesUsed", bytesUsed()); + .add("bytesUsed", bytesUsed()) // + .add("metadata", metadata()); } @Override @@ -108,6 +129,7 @@ public class Container implements Comparable<Container> { protected String name; protected long objectCount; protected long bytesUsed; + protected Map<String, String> metadata = ImmutableMap.of(); /** * @see Container#name() @@ -133,14 +155,30 @@ public class Container implements Comparable<Container> { return this; } + /** + * Will lower-case all metadata keys due to a swift implementation + * decision. + * + * @see Container#metadata() + */ + public Builder metadata(Map<String, String> metadata) { + ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String> builder(); + for (Entry<String, String> entry : checkNotNull(metadata, "metadata").entrySet()) { + builder.put(entry.getKey().toLowerCase(), entry.getValue()); + } + this.metadata = builder.build(); + return this; + } + public Container build() { - return new Container(name, objectCount, bytesUsed); + return new Container(name, objectCount, bytesUsed, metadata); } public Builder fromContainer(Container from) { return name(from.name()) // .objectCount(from.objectCount()) // - .bytesUsed(from.bytesUsed()); + .bytesUsed(from.bytesUsed()) // + .metadata(from.metadata()); } } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java index 5696d77..a1b7ae7 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java @@ -16,17 +16,36 @@ */ package org.jclouds.openstack.swift.v1.features; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import java.util.Map; + +import javax.inject.Named; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; -import javax.ws.rs.core.MediaType; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404; +import org.jclouds.Fallbacks.FalseOnNotFoundOr404; +import org.jclouds.Fallbacks.NullOnNotFoundOr404; +import org.jclouds.javax.annotation.Nullable; import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindContainerMetadataToHeaders; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveContainerMetadataToHeaders; import org.jclouds.openstack.swift.v1.domain.Container; -import org.jclouds.openstack.swift.v1.options.ListContainersOptions; +import org.jclouds.openstack.swift.v1.functions.FalseOnAccepted; +import org.jclouds.openstack.swift.v1.functions.ParseContainerFromHeaders; +import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; import com.google.common.collect.FluentIterable; @@ -40,31 +59,132 @@ import com.google.common.collect.FluentIterable; * >api doc</a> */ @RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) public interface ContainerApi { /** - * @see #list(ListContainersOptions) + * Lists up to 10,000 containers. + * + * @return a list of existing storage containers ordered by name. */ + @Named("ListContainers") @GET - @Consumes(MediaType.APPLICATION_JSON) @QueryParams(keys = "format", values = "json") @Fallback(EmptyFluentIterableOnNotFoundOr404.class) @Path("/") - FluentIterable<? extends Container> list(); + FluentIterable<Container> listFirstPage(); /** - * retrieve a list of existing storage containers ordered by name. The sort order for the name is - * based on a binary comparison, a single built-in collating sequence that compares string data - * using SQLite's memcmp() function, regardless of text encoding. + * Lists up to 10,000 containers, starting at {@code marker} + * + * @param marker + * lexicographic position to start list. * - * @param options * @return a list of existing storage containers ordered by name. */ + @Named("ListContainers") @GET - @Consumes(MediaType.APPLICATION_JSON) @QueryParams(keys = "format", values = "json") @Fallback(EmptyFluentIterableOnNotFoundOr404.class) @Path("/") - FluentIterable<? extends Container> list(ListContainersOptions options); + FluentIterable<Container> listAt(@QueryParam("marker") String marker); + + /** + * Creates a container, if not already present. + * + * @param containerName + * corresponds to {@link Container#name()}. + * @see <a + * href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/create-container.html"> + * Create Container API</a> + * + * @return <code>false</code> if the container already existed. + */ + @Named("CreateContainer") + @PUT + @ResponseParser(FalseOnAccepted.class) + @Path("/{containerName}") + boolean createIfAbsent(@PathParam("containerName") String containerName); + + /** + * Gets the {@link Container}. + * + * @param containerName + * corresponds to {@link Container#name()}. + * @return the Container or null, if not found. + * + * @see <a + * href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/retrieve-Container-metadata.html"> + * Get Container Metadata API</a> + */ + @Named("GetContainer") + @HEAD + @ResponseParser(ParseContainerFromHeaders.class) + @Fallback(NullOnNotFoundOr404.class) + @Path("/{containerName}") + @Nullable + Container get(@PathParam("containerName") String containerName); + /** + * Creates or updates the Container metadata. + * + * @param containerName + * corresponds to {@link Container#name()}. + * @param metadata + * the Container metadata to create or update. + * + * @see <a + * href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/Update_Container_Metadata-d1e1900.html"> + * Create or Update Container Metadata API</a> + * + * @return <code>true</code> if the Container Metadata was successfully + * created or updated, false if not. + */ + @Named("UpdateContainerMetadata") + @POST + @Fallback(FalseOnNotFoundOr404.class) + @Path("/{containerName}") + boolean updateMetadata(@PathParam("containerName") String containerName, + @BinderParam(BindContainerMetadataToHeaders.class) Map<String, String> metadata); + + /** + * Deletes Container metadata. + * + * @param containerName + * corresponds to {@link Container#name()}. + * @param metadata + * the Container metadata to delete. + * + * @return <code>true</code> if the Container Metadata was successfully + * deleted, false if not. + * + * @see <a + * href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/delete-container-metadata.html"> + * Delete Container Metadata API</a> + */ + @Named("DeleteContainerMetadata") + @POST + @Fallback(FalseOnNotFoundOr404.class) + @Path("/{containerName}") + boolean deleteMetadata(@PathParam("containerName") String containerName, + @BinderParam(BindRemoveContainerMetadataToHeaders.class) Map<String, String> metadata); + + /** + * Deletes a container, if empty. + * + * @param containerName + * corresponds to {@link Container#name()}. + * @see <a + * href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/delete-container.html"> + * Delete Container API</a> + * + * @return <code>false</code> if the container was not present. + * @throws IllegalStateException + * when the container wasn't empty. + */ + @Named("DeleteContainer") + @DELETE + @Fallback(FalseOnNotFoundOr404.class) + @Path("/{containerName}") + boolean deleteIfEmpty(@PathParam("containerName") String containerName) throws IllegalStateException; } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java new file mode 100644 index 0000000..68da524 --- /dev/null +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java @@ -0,0 +1,30 @@ +/* + * 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.openstack.swift.v1.functions; + +import org.jclouds.http.HttpResponse; + +import com.google.common.base.Function; + +/** Returns {@code false} on HTTP 202 {@code Accepted}. */ +public class FalseOnAccepted implements Function<HttpResponse, Boolean> { + + @Override + public Boolean apply(HttpResponse from) { + return from.getStatusCode() == 202 ? false : true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java new file mode 100644 index 0000000..5e66202 --- /dev/null +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java @@ -0,0 +1,46 @@ +/* + * 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.openstack.swift.v1.functions; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.rest.InvocationContext; +import org.jclouds.rest.internal.GeneratedHttpRequest; + +import com.google.common.base.Function; + +public class ParseContainerFromHeaders implements Function<HttpResponse, Container>, + InvocationContext<ParseContainerFromHeaders> { + + private String name; + + @Override + public Container apply(HttpResponse from) { + return Container.builder() // + .name(name) // + .bytesUsed(Long.parseLong(from.getFirstHeaderOrNull("X-Container-Bytes-Used"))) // + .objectCount(Integer.parseInt(from.getFirstHeaderOrNull("X-Container-Object-Count"))) // + .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build(); + } + + @Override + public ParseContainerFromHeaders setContext(HttpRequest request) { + this.name = GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString(); + return this; + } +} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java index e71daa6..f94f4a8 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java @@ -65,6 +65,9 @@ public class SwiftErrorHandler implements HttpErrorHandler { } } break; + case 409: + exception = new IllegalStateException(exception.getMessage(), exception); + break; } command.setException(exception); } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java deleted file mode 100644 index 2efa90b..0000000 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java +++ /dev/null @@ -1,77 +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.openstack.swift.v1.options; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import org.jclouds.http.options.BaseHttpRequestOptions; - -/** - * Contains options supported in the REST API for the GET container operation. <h2> - */ -public class ListContainersOptions extends BaseHttpRequestOptions { - public static final ListContainersOptions NONE = new ListContainersOptions(); - - /** - * Given a string value x, return object names greater in value than the specified marker. - */ - public ListContainersOptions marker(String marker) { - queryParameters.put("marker", checkNotNull(marker, "marker")); - return this; - } - - public String getMarker() { - return getFirstQueryOrNull("marker"); - } - - /** - * For an integer value n, limits the number of results to n values. - */ - public ListContainersOptions limit(int limit) { - checkState(limit >= 0, "limit must be >= 0"); - checkState(limit <= 10000, "limit must be <= 10000"); - queryParameters.put("limit", Integer.toString(limit)); - return this; - } - - public int getLimit() { - String val = getFirstQueryOrNull("limit"); - return val != null ? Integer.valueOf(val) : 10000; - } - - - public static class Builder { - - /** - * @see ListContainersOptions#marker(String) - */ - public static ListContainersOptions marker(String marker) { - ListContainersOptions options = new ListContainersOptions(); - return options.marker(marker); - } - - /** - * @see ListContainersOptions#limit(int) - */ - public static ListContainersOptions limit(int limit) { - ListContainersOptions options = new ListContainersOptions(); - return options.limit(limit); - } - - } -} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java index 93b248e..973d363 100644 --- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java +++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java @@ -16,14 +16,22 @@ */ package org.jclouds.openstack.swift.v1.features; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.util.Map; +import java.util.Map.Entry; + import org.jclouds.openstack.swift.v1.domain.Container; import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableMap; /** * @author Adrian Cole @@ -31,11 +39,13 @@ import com.google.common.collect.FluentIterable; @Test(groups = "live", testName = "ContainerApiLiveTest") public class ContainerApiLiveTest extends BaseSwiftApiLiveTest { + private String name = getClass().getSimpleName(); + @Test - public void testListContainers() throws Exception { + public void list() throws Exception { for (String regionId : api.configuredRegions()) { ContainerApi containerApi = api.containerApiInRegion(regionId); - FluentIterable<? extends Container> response = containerApi.list(); + FluentIterable<Container> response = containerApi.listFirstPage(); assertNotNull(response); for (Container container : response) { assertNotNull(container.name()); @@ -44,4 +54,80 @@ public class ContainerApiLiveTest extends BaseSwiftApiLiveTest { } } } + + public void get() throws Exception { + for (String regionId : api.configuredRegions()) { + Container container = api.containerApiInRegion(regionId).get(name); + assertEquals(container.name(), name); + assertTrue(container.objectCount() == 0); + assertTrue(container.bytesUsed() == 0); + } + } + + public void listAt() throws Exception { + String lexicographicallyBeforeName = name.substring(0, name.length() - 1); + for (String regionId : api.configuredRegions()) { + Container container = api.containerApiInRegion(regionId).listAt(lexicographicallyBeforeName).get(0); + assertEquals(container.name(), name); + assertTrue(container.objectCount() == 0); + assertTrue(container.bytesUsed() == 0); + } + } + + public void updateMetadata() throws Exception { + for (String regionId : api.configuredRegions()) { + ContainerApi containerApi = api.containerApiInRegion(regionId); + + Map<String, String> meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar"); + + assertTrue(containerApi.updateMetadata(name, meta)); + + containerHasMetadata(containerApi, name, meta); + } + } + + public void deleteMetadata() throws Exception { + for (String regionId : api.configuredRegions()) { + ContainerApi containerApi = api.containerApiInRegion(regionId); + + Map<String, String> meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar"); + + assertTrue(containerApi.updateMetadata(name, meta)); + containerHasMetadata(containerApi, name, meta); + + assertTrue(containerApi.deleteMetadata(name, meta)); + Container container = containerApi.get(name); + for (Entry<String, String> entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertFalse(container.metadata().containsKey(entry.getKey().toLowerCase())); + } + } + } + + static void containerHasMetadata(ContainerApi containerApi, String name, Map<String, String> meta) { + Container container = containerApi.get(name); + for (Entry<String, String> entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertEquals(container.metadata().get(entry.getKey().toLowerCase()), entry.getValue(), // + container + " didn't have metadata: " + entry); + } + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + for (String regionId : api.configuredRegions()) { + api.containerApiInRegion(regionId).createIfAbsent(name); + } + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : api.configuredRegions()) { + api.containerApiInRegion(regionId).deleteIfEmpty(name); + } + super.tearDown(); + } } http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java new file mode 100644 index 0000000..d54530e --- /dev/null +++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java @@ -0,0 +1,280 @@ +/* + * 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.openstack.swift.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.openstack.swift.v1.internal.BaseSwiftMockTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +/** + * @author Adrian Cole + */ +@Test +public class ContainerApiMockTest extends BaseSwiftMockTest { + + String containerList = "" // + + "[\n" // + + " {\"name\":\"test_container_1\", \"count\":2, \"bytes\":78},\n" // + + " {\"name\":\"test_container_2\", \"count\":1, \"bytes\":17}\n" // + + "]"; + + public void listFirstPage() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setBody(containerList)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + ImmutableList<Container> containers = api.containerApiInRegion("DFW").listFirstPage().toList(); + assertEquals(containers, ImmutableList.of(// + Container.builder() // + .name("test_container_1") // + .objectCount(2) // + .bytesUsed(78).build(), // + Container.builder() // + .name("test_container_2") // + .objectCount(1) // + .bytesUsed(17).build())); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + assertEquals(server.takeRequest().getRequestLine(), + "GET /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/?format=json HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void listAt() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setBody(containerList)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + ImmutableList<Container> containers = api.containerApiInRegion("DFW").listAt("test").toList(); + assertEquals(containers, ImmutableList.of(// + Container.builder() // + .name("test_container_1") // + .objectCount(2) // + .bytesUsed(78).build(), // + Container.builder() // + .name("test_container_2") // + .objectCount(1) // + .bytesUsed(17).build())); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + assertEquals(server.takeRequest().getRequestLine(), + "GET /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/?format=json&marker=test HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void createIfAbsent() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setResponseCode(201)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertTrue(api.containerApiInRegion("DFW").createIfAbsent("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "PUT /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void alreadyCreated() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setResponseCode(202)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertFalse(api.containerApiInRegion("DFW").createIfAbsent("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "PUT /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + /** upper-cases first char, and lower-cases rest!! **/ + public void getKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(containerResponse() // + // note silly casing + .addHeader("X-Container-Meta-Apiname", "swift") // + .addHeader("X-Container-Meta-Apiversion", "v1.1")); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + Container container = api.containerApiInRegion("DFW").get("myContainer"); + assertEquals(container.name(), "myContainer"); + assertEquals(container.objectCount(), 42l); + assertEquals(container.bytesUsed(), 323479l); + for (Entry<String, String> entry : container.metadata().entrySet()) { + assertEquals(container.metadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + assertEquals(server.takeRequest().getRequestLine(), + "HEAD /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void updateMetadata() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(containerResponse() // + .addHeader("X-Container-Meta-ApiName", "swift") // + .addHeader("X-Container-Meta-ApiVersion", "v1.1")); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertTrue(api.containerApiInRegion("DFW").updateMetadata("myContainer", metadata)); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest replaceRequest = server.takeRequest(); + assertEquals(replaceRequest.getRequestLine(), + "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + for (Entry<String, String> entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader("x-container-meta-" + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void deleteMetadata() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(containerResponse()); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertTrue(api.containerApiInRegion("DFW").deleteMetadata("myContainer", metadata)); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + for (String key : metadata.keySet()) { + assertEquals(deleteRequest.getHeader("x-remove-container-meta-" + key.toLowerCase()), "ignored"); + } + } finally { + server.shutdown(); + } + } + + public void deleteIfEmpty() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setResponseCode(204)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertTrue(api.containerApiInRegion("DFW").deleteIfEmpty("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void alreadyDeleted() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setResponseCode(404)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + assertFalse(api.containerApiInRegion("DFW").deleteIfEmpty("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test(expectedExceptions = IllegalStateException.class) + public void deleteWhenNotEmpty() throws Exception { + MockWebServer server = mockSwiftServer(); + server.enqueue(new MockResponse().setBody(access)); + server.enqueue(new MockResponse().setResponseCode(409)); + + try { + SwiftApi api = swiftApi(server.getUrl("/").toString()); + api.containerApiInRegion("DFW").deleteIfEmpty("myContainer"); + + } finally { + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest deleteRequest = server.takeRequest(); + assertEquals(deleteRequest.getRequestLine(), + "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1"); + server.shutdown(); + } + } + + private final static Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1"); + + public static MockResponse containerResponse() { + return new MockResponse() // + .addHeader("X-Container-Object-Count", "42") // + .addHeader("X-Container-Bytes-Used", "323479"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java deleted file mode 100644 index df9d4d6..0000000 --- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java +++ /dev/null @@ -1,92 +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.openstack.swift.v1.options; - -import static org.jclouds.openstack.swift.v1.options.ListContainersOptions.Builder.limit; -import static org.jclouds.openstack.swift.v1.options.ListContainersOptions.Builder.marker; -import static org.testng.Assert.assertEquals; - -import com.google.common.collect.ImmutableList; - -import org.jclouds.http.options.HttpRequestOptions; -import org.testng.annotations.Test; - -/** - * @author Adrian Cole - */ -@Test(testName = "ListContainersOptionsTest") -public class ListContainersOptionsTest { - - @Test - public void testAssignability() { - assert HttpRequestOptions.class.isAssignableFrom(ListContainersOptions.class); - assert !String.class.isAssignableFrom(ListContainersOptions.class); - } - @Test - public void testNoOptionsQueryString() { - HttpRequestOptions options = new ListContainersOptions(); - assertEquals(options.buildQueryParameters().size(), 0); - } - - @Test - public void testMarker() { - ListContainersOptions options = new ListContainersOptions(); - options.marker("test"); - assertEquals(options.buildQueryParameters().get("marker"), ImmutableList.of("test")); - } - - @Test - public void testNullMarker() { - ListContainersOptions options = new ListContainersOptions(); - assertEquals(options.buildQueryParameters().get("marker"), ImmutableList.of()); - } - - @Test - public void testMarkerStatic() { - ListContainersOptions options = marker("test"); - assertEquals(options.buildQueryParameters().get("marker"), ImmutableList.of("test")); - } - - @Test(expectedExceptions = NullPointerException.class) - public void testMarkerNPE() { - marker(null); - } - - @Test - public void testLimit() { - ListContainersOptions options = new ListContainersOptions(); - options.limit(1000); - assertEquals(options.buildQueryParameters().get("limit"), ImmutableList.of("1000")); - } - - @Test - public void testNullLimit() { - ListContainersOptions options = new ListContainersOptions(); - assertEquals(options.buildQueryParameters().get("limit"), ImmutableList.of()); - } - - @Test - public void testLimitStatic() { - ListContainersOptions options = limit(1000); - assertEquals(options.buildQueryParameters().get("limit"), ImmutableList.of("1000")); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testLimitNegative() { - limit(-1); - } -} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java deleted file mode 100644 index 4101fb2..0000000 --- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java +++ /dev/null @@ -1,56 +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.openstack.swift.v1.parse; - -import java.util.Set; - -import javax.ws.rs.Consumes; -import javax.ws.rs.core.MediaType; - -import org.jclouds.json.BaseSetParserTest; -import org.jclouds.openstack.swift.v1.domain.Container; -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableSet; - -/** - * @author Adrian Cole - */ -@Test(groups = "unit", testName = "ParseContainerListTest") -public class ParseContainerListTest extends BaseSetParserTest<Container> { - - @Override - public String resource() { - return "/container_list.json"; - } - - @Override - @Consumes(MediaType.APPLICATION_JSON) - public Set<Container> expected() { - return ImmutableSet - .of(Container.builder() - .name("test_container_1") - .objectCount(2) - .bytesUsed(78) - .build(), - Container.builder() - .name("test_container_2") - .objectCount(1) - .bytesUsed(17) - .build()); - } -} http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/resources/container_list.json ---------------------------------------------------------------------- diff --git a/openstack-swift/src/test/resources/container_list.json b/openstack-swift/src/test/resources/container_list.json deleted file mode 100644 index f7d9b9b..0000000 --- a/openstack-swift/src/test/resources/container_list.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - {"name":"test_container_1", "count":2, "bytes":78}, - {"name":"test_container_2", "count":1, "bytes":17} -] \ No newline at end of file
