From 8505539a09b9567f153efe057fad8ce1caf8a64f Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 8 Oct 2014 08:18:47 -0700 Subject: [PATCH] Import openstack-swift from labs. --- allblobstore/pom.xml | 5 + apis/openstack-swift/README.md | 10 + apis/openstack-swift/pom.xml | 141 +++++ .../swift/v1/CopyObjectException.java | 48 ++ .../jclouds/openstack/swift/v1/SwiftApi.java | 70 +++ .../openstack/swift/v1/SwiftApiMetadata.java | 99 ++++ .../openstack/swift/v1/SwiftFallbacks.java | 43 ++ .../swift/v1/TemporaryUrlSigner.java | 89 +++ .../v1/binders/BindMetadataToHeaders.java | 141 +++++ .../swift/v1/binders/SetPayload.java | 72 +++ .../RegionScopedBlobStoreContext.java | 154 ++++++ .../blobstore/RegionScopedSwiftBlobStore.java | 312 +++++++++++ .../RegionScopedTemporaryUrlBlobSigner.java | 109 ++++ .../config/SignUsingTemporaryUrls.java | 67 +++ .../config/SwiftBlobStoreContextModule.java | 65 +++ .../blobstore/functions/ToBlobMetadata.java | 71 +++ .../functions/ToListContainerOptions.java | 52 ++ .../functions/ToResourceMetadata.java | 44 ++ .../v1/config/BaseSwiftHttpApiModule.java | 46 ++ .../swift/v1/config/SwiftHttpApiModule.java | 32 ++ .../swift/v1/config/SwiftTypeAdapters.java | 119 ++++ .../openstack/swift/v1/domain/Account.java | 219 ++++++++ .../swift/v1/domain/BulkDeleteResponse.java | 101 ++++ .../openstack/swift/v1/domain/Container.java | 237 ++++++++ .../v1/domain/ExtractArchiveResponse.java | 90 +++ .../openstack/swift/v1/domain/ObjectList.java | 57 ++ .../openstack/swift/v1/domain/Segment.java | 136 +++++ .../swift/v1/domain/SwiftObject.java | 267 +++++++++ .../swift/v1/features/AccountApi.java | 111 ++++ .../openstack/swift/v1/features/BulkApi.java | 105 ++++ .../swift/v1/features/ContainerApi.java | 215 ++++++++ .../swift/v1/features/ObjectApi.java | 265 +++++++++ .../v1/features/StaticLargeObjectApi.java | 90 +++ .../swift/v1/functions/ETagHeader.java | 32 ++ .../functions/EntriesWithoutMetaPrefix.java | 48 ++ .../swift/v1/functions/FalseOnAccepted.java | 30 + .../v1/functions/MetadataFromHeaders.java | 31 ++ .../v1/functions/ParseAccountFromHeaders.java | 38 ++ .../functions/ParseContainerFromHeaders.java | 54 ++ .../v1/functions/ParseObjectFromResponse.java | 87 +++ .../ParseObjectListFromResponse.java | 113 ++++ .../swift/v1/handlers/SwiftErrorHandler.java | 91 ++++ .../v1/options/CreateContainerOptions.java | 108 ++++ .../v1/options/ListContainerOptions.java | 132 +++++ .../swift/v1/options/PutOptions.java | 71 +++ .../v1/options/UpdateContainerOptions.java | 107 ++++ .../swift/v1/reference/SwiftHeaders.java | 92 ++++ .../services/org.jclouds.apis.ApiMetadata | 18 + .../swift/v1/AuthenticationMockTest.java | 71 +++ .../swift/v1/SwiftApiMetadataTest.java | 32 ++ .../swift/v1/SwiftErrorHandlerTest.java | 117 ++++ .../swift/v1/TemporaryUrlSignerLiveTest.java | 90 +++ .../swift/v1/TemporaryUrlSignerMockTest.java | 76 +++ .../RegionScopedBlobStoreContextLiveTest.java | 159 ++++++ .../SwiftBlobIntegrationLiveTest.java | 76 +++ .../integration/SwiftBlobLiveTest.java | 39 ++ .../integration/SwiftBlobSignerLiveTest.java | 39 ++ .../SwiftContainerIntegrationLiveTest.java | 54 ++ .../integration/SwiftContainerLiveTest.java | 39 ++ .../SwiftServiceIntegrationLiveTest.java | 39 ++ .../v1/config/SwiftTypeAdaptersTest.java | 90 +++ .../swift/v1/features/AccountApiLiveTest.java | 87 +++ .../swift/v1/features/AccountApiMockTest.java | 145 +++++ .../swift/v1/features/BulkApiLiveTest.java | 125 +++++ .../swift/v1/features/BulkApiMockTest.java | 72 +++ .../v1/features/ContainerApiLiveTest.java | 197 +++++++ .../v1/features/ContainerApiMockTest.java | 363 ++++++++++++ .../CreatePublicContainerLiveTest.java | 47 ++ .../swift/v1/features/ObjectApiLiveTest.java | 286 ++++++++++ .../swift/v1/features/ObjectApiMockTest.java | 515 ++++++++++++++++++ .../StaticLargeObjectApiLiveTest.java | 129 +++++ .../StaticLargeObjectApiMockTest.java | 107 ++++ .../UrlEncodeAndJoinOnNewlineTest.java | 44 ++ .../v1/internal/BaseSwiftApiLiveTest.java | 88 +++ .../options/CreateContainerOptionsTest.java | 69 +++ .../swift/v1/options/PutOptionsTest.java | 48 ++ .../options/UpdateContainerOptionsTest.java | 122 +++++ .../src/test/resources/access.json | 249 +++++++++ .../src/test/resources/container_list.json | 12 + .../src/test/resources/logback.xml | 71 +++ .../src/test/resources/object_list.json | 23 + apis/pom.xml | 1 + 82 files changed, 8455 insertions(+) create mode 100644 apis/openstack-swift/README.md create mode 100644 apis/openstack-swift/pom.xml create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java create mode 100644 apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java create mode 100644 apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/internal/BaseSwiftApiLiveTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java create mode 100644 apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java create mode 100644 apis/openstack-swift/src/test/resources/access.json create mode 100644 apis/openstack-swift/src/test/resources/container_list.json create mode 100644 apis/openstack-swift/src/test/resources/logback.xml create mode 100644 apis/openstack-swift/src/test/resources/object_list.json diff --git a/allblobstore/pom.xml b/allblobstore/pom.xml index 4adce398a5..5229953c01 100644 --- a/allblobstore/pom.xml +++ b/allblobstore/pom.xml @@ -53,6 +53,11 @@ swift ${project.version} + + org.apache.jclouds.api + openstack-swift + ${project.version} + org.apache.jclouds.api cloudfiles diff --git a/apis/openstack-swift/README.md b/apis/openstack-swift/README.md new file mode 100644 index 0000000000..59bd846911 --- /dev/null +++ b/apis/openstack-swift/README.md @@ -0,0 +1,10 @@ +OpenStack Swift +=============== + +Swift is a distributed object storage system designed to scale from a single machine to thousands of servers. + +Production ready? +Beta + +This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based on that feedback, minor changes to the interfaces may happen. This code will replace org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later. + diff --git a/apis/openstack-swift/pom.xml b/apis/openstack-swift/pom.xml new file mode 100644 index 0000000000..23333c193f --- /dev/null +++ b/apis/openstack-swift/pom.xml @@ -0,0 +1,141 @@ + + + + 4.0.0 + + org.apache.jclouds + jclouds-project + 2.0.0-SNAPSHOT + ../../project/pom.xml + + + org.apache.jclouds.api + openstack-swift + jclouds openstack-swift api + jclouds components to access an implementation of OpenStack Swift + bundle + + + + http://localhost:5000/v2.0/ + 1.0 + + FIXME_IDENTITY + FIXME_CREDENTIALS + passwordCredentials + http://archive.apache.org/dist/commons/logging/binaries/commons-logging-1.1.1-bin.tar.gz + e5de09672af9b386c30a311654d8541a + org.jclouds.openstack.swift.v1*;version="${project.version}" + org.jclouds*;version="${project.version}",* + + + + + org.apache.jclouds.api + openstack-keystone + ${project.version} + + + org.apache.jclouds + jclouds-blobstore + ${project.version} + + + org.apache.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.apache.jclouds.api + openstack-keystone + ${project.version} + test-jar + test + + + org.apache.jclouds + jclouds-blobstore + ${project.version} + test-jar + test + + + org.jboss.shrinkwrap + shrinkwrap-depchain + 1.2.0 + pom + test + + + org.apache.jclouds.driver + jclouds-slf4j + ${project.version} + test + + + ch.qos.logback + logback-classic + test + + + com.squareup.okhttp + mockwebserver + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.openstack-swift.endpoint} + ${test.openstack-swift.api-version} + ${test.openstack-swift.build-version} + ${test.openstack-swift.identity} + ${test.openstack-swift.credential} + ${test.jclouds.keystone.credential-type} + ${jclouds.blobstore.httpstream.url} + ${jclouds.blobstore.httpstream.md5} + + + + + + + + + + + diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java new file mode 100644 index 0000000000..a7fd09d375 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java @@ -0,0 +1,48 @@ +/* + * 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; + +import org.jclouds.rest.ResourceNotFoundException; + +/** + * Thrown when an object cannot be copied. + * + * + * @see {@link SwiftErrorHandler#handleError(HttpCommand, HttpResponse)} + */ +@SuppressWarnings("serial") +public class CopyObjectException extends ResourceNotFoundException { + + private String sourcePath; + private String destinationPath; + + public CopyObjectException(String sourcePath, String destinationPath, String message) { + super(String.format("Either the source path '%s' or the destination path '%s' was not found. " + + "(message: %s)", sourcePath, destinationPath, message)); + this.sourcePath = sourcePath; + this.destinationPath = destinationPath; + } + + public String getSourcePath() { + return sourcePath; + } + + public String getDestinationPath() { + return destinationPath; + } + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java new file mode 100644 index 0000000000..1d4d1d0ee5 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.io.Closeable; +import java.util.Set; + +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.jclouds.location.Region; +import org.jclouds.location.functions.RegionToEndpoint; +import org.jclouds.openstack.swift.v1.features.AccountApi; +import org.jclouds.openstack.swift.v1.features.BulkApi; +import org.jclouds.openstack.swift.v1.features.ContainerApi; +import org.jclouds.openstack.swift.v1.features.ObjectApi; +import org.jclouds.openstack.swift.v1.features.StaticLargeObjectApi; +import org.jclouds.rest.annotations.Delegate; +import org.jclouds.rest.annotations.EndpointParam; + +import com.google.common.annotations.Beta; +import com.google.inject.Provides; + +/** + * Provides access to the OpenStack Object Storage (Swift) API. + *

+ * OpenStack Object Storage is an object-based storage system that stores content and metadata + * as objects. You create, modify, and get objects and metadata using this API. + *

+ */ +@Beta +public interface SwiftApi extends Closeable { + + @Provides + @Region + Set getConfiguredRegions(); + + @Delegate + AccountApi getAccountApi(@EndpointParam(parser = RegionToEndpoint.class) String region); + + @Delegate + BulkApi getBulkApi(@EndpointParam(parser = RegionToEndpoint.class) String region); + + @Delegate + ContainerApi getContainerApi(@EndpointParam(parser = RegionToEndpoint.class) String region); + + @Delegate + @Path("/{containerName}") + ObjectApi getObjectApi(@EndpointParam(parser = RegionToEndpoint.class) String region, + @PathParam("containerName") String containerName); + + @Delegate + @Path("/{containerName}") + StaticLargeObjectApi getStaticLargeObjectApi(@EndpointParam(parser = RegionToEndpoint.class) String region, + @PathParam("containerName") String containerName); +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java new file mode 100644 index 0000000000..8d77bb058e --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java @@ -0,0 +1,99 @@ +/* + * 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; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.SERVICE_TYPE; +import static org.jclouds.reflect.Reflection2.typeToken; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.openstack.keystone.v2_0.config.AuthenticationApiModule; +import org.jclouds.openstack.keystone.v2_0.config.CredentialTypes; +import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule; +import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext; +import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls; +import org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule; +import org.jclouds.openstack.swift.v1.config.SwiftHttpApiModule; +import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters; +import org.jclouds.openstack.v2_0.ServiceType; +import org.jclouds.rest.internal.BaseHttpApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; + +/** + * Implementation of {@link ApiMetadata} for the Swift API. + */ +public class SwiftApiMetadata extends BaseHttpApiMetadata { + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public SwiftApiMetadata() { + this(new Builder()); + } + + protected SwiftApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseHttpApiMetadata.defaultProperties(); + properties.setProperty(SERVICE_TYPE, ServiceType.OBJECT_STORE); + properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS); + return properties; + } + + public static class Builder extends BaseHttpApiMetadata.Builder { + + protected Builder() { + id("openstack-swift") + .name("OpenStack Swift API") + .identityName("${tenantName}:${userName} or ${userName}, if your keystone supports a default tenant") + .credentialName("${password}") + .documentation(URI.create("http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-overview.html")) + .version("1") + .endpointName("Keystone base url ending in /v2.0/") + .defaultEndpoint("http://localhost:5000/v2.0/") + .defaultProperties(SwiftApiMetadata.defaultProperties()) + .view(typeToken(RegionScopedBlobStoreContext.class)) + .defaultModules(ImmutableSet.>builder() + .add(AuthenticationApiModule.class) + .add(KeystoneAuthenticationModule.class) + .add(RegionModule.class) + .add(SwiftTypeAdapters.class) + .add(SwiftHttpApiModule.class) + .add(SwiftBlobStoreContextModule.class) + .add(SignUsingTemporaryUrls.class).build()); + } + + @Override + public SwiftApiMetadata build() { + return new SwiftApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java new file mode 100644 index 0000000000..c875707e41 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java @@ -0,0 +1,43 @@ +/* + * 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; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Throwables.propagate; +import static org.jclouds.http.HttpUtils.contains404; +import static org.jclouds.http.HttpUtils.returnValueOnCodeOrNull; + +import org.jclouds.Fallback; + +public final class SwiftFallbacks { + + public static final class TrueOn404FalseOn409 implements Fallback { + @Override + public Boolean createOrPropagate(Throwable t) throws Exception { + if (contains404(checkNotNull(t, "throwable"))) + return true; + if (returnValueOnCodeOrNull(t, false, equalTo(409)) != null) + return false; + throw propagate(t); + } + } + + private SwiftFallbacks() { + throw new AssertionError("intentionally unimplemented"); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java new file mode 100644 index 0000000000..c6d60300f8 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java @@ -0,0 +1,89 @@ +/* + * 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; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Suppliers.memoizeWithExpiration; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.io.BaseEncoding.base16; +import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.SECONDS; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.jclouds.openstack.swift.v1.features.AccountApi; + +import com.google.common.base.Supplier; + +/** + * Use this utility to create temporary urls. + */ +public class TemporaryUrlSigner { + + public static TemporaryUrlSigner checkApiEvery(final AccountApi api, long seconds) { + Supplier keySupplier = memoizeWithExpiration(new TemporaryUrlKeyFromAccount(api), seconds, SECONDS); + return new TemporaryUrlSigner(keySupplier); + } + + private final Supplier keySupplier; + + TemporaryUrlSigner(Supplier keySupplier) { + this.keySupplier = keySupplier; + } + + public String sign(String method, String path, long expirationTimestampSeconds) { + checkNotNull(method, "method"); + checkNotNull(path, "path"); + checkArgument(expirationTimestampSeconds > 0, "expirationTimestamp must be a unix epoch timestamp"); + String hmacBody = format("%s\n%s\n%s", method, expirationTimestampSeconds, path); + return base16().lowerCase().encode(hmacSHA1(hmacBody)); + } + + byte[] hmacSHA1(String data) { + try { + String key = keySupplier.get(); + checkState(key != null, "%s returned a null temporaryUrlKey!", keySupplier); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(key.getBytes(UTF_8), "HmacSHA1")); + return mac.doFinal(data.getBytes(UTF_8)); + } catch (Exception e) { + throw propagate(e); + } + } + + static class TemporaryUrlKeyFromAccount implements Supplier { + private final AccountApi api; + + private TemporaryUrlKeyFromAccount(AccountApi api) { + this.api = checkNotNull(api, "accountApi"); + } + + @Override + public String get() { + return api.get().getTemporaryUrlKey().orNull(); + } + + @Override + public String toString() { + return format("get().getTemporaryUrlKey() using %s", api); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java new file mode 100644 index 0000000000..4dabc68b40 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java @@ -0,0 +1,141 @@ +/* + * 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.binders; + +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultimap.Builder; + +/** + * Will lower-case header keys due to a swift implementation to return headers + * in a different case than sent. ex. + * + *

+ * >> X-Account-Meta-MyDelete1: foo
+ * >> X-Account-Meta-MyDelete2: bar
+ * 
+ * + * results in: + * + *
+ * << X-Account-Meta-Mydelete1: foo
+ * << X-Account-Meta-Mydelete2: bar
+ * 
+ * + *

Note


+ * HTTP response headers keys are known to be case-insensitive, but this + * practice of mixing up case will prevent metadata keys such as those in + * Turkish from working. + */ +public class BindMetadataToHeaders implements Binder { + + public static class BindAccountMetadataToHeaders extends BindMetadataToHeaders { + BindAccountMetadataToHeaders() { + super(ACCOUNT_METADATA_PREFIX); + } + } + + public static class BindRemoveAccountMetadataToHeaders extends BindMetadataToHeaders.ForRemoval { + BindRemoveAccountMetadataToHeaders() { + super(ACCOUNT_METADATA_PREFIX); + } + } + + public static class BindContainerMetadataToHeaders extends BindMetadataToHeaders { + BindContainerMetadataToHeaders() { + super(CONTAINER_METADATA_PREFIX); + } + } + + public static class BindRemoveContainerMetadataToHeaders extends BindMetadataToHeaders.ForRemoval { + BindRemoveContainerMetadataToHeaders() { + super(CONTAINER_METADATA_PREFIX); + } + } + + public static class BindObjectMetadataToHeaders extends BindMetadataToHeaders { + BindObjectMetadataToHeaders() { + super(OBJECT_METADATA_PREFIX); + } + } + + public static class BindRemoveObjectMetadataToHeaders extends BindMetadataToHeaders.ForRemoval { + BindRemoveObjectMetadataToHeaders() { + super(OBJECT_METADATA_PREFIX); + } + } + + /** + * @see documentation + */ + public abstract static class ForRemoval extends BindMetadataToHeaders { + ForRemoval(String metadataPrefix) { + super(metadataPrefix); + } + + @Override + protected void putMetadata(Builder headers, String key, String value) { + headers.put(String.format("x-remove%s", key.substring(1)), "ignored"); + } + } + + private final String metadataPrefix; + + public BindMetadataToHeaders(String metadataPrefix) { + this.metadataPrefix = checkNotNull(metadataPrefix, "metadataPrefix"); + } + + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + checkNotNull(request, "request"); + checkArgument(input instanceof Map, "input must be a non-null java.util.Map!"); + Map metadata = Map.class.cast(input); + ImmutableMultimap headers = toHeaders(metadata); + return (R) request.toBuilder().replaceHeaders(headers).build(); + } + + protected void putMetadata(Builder headers, String key, String value) { + headers.put(key, value); + } + + public ImmutableMultimap toHeaders(Map metadata) { + Builder builder = ImmutableMultimap. builder(); + for (Entry keyVal : metadata.entrySet()) { + String keyInLowercase = keyVal.getKey().toLowerCase(); + if (keyVal.getKey().startsWith(metadataPrefix)) { + putMetadata(builder, keyInLowercase, keyVal.getValue()); + } else { + putMetadata(builder, String.format("%s%s", metadataPrefix, keyInLowercase), keyVal.getValue()); + } + } + return builder.build(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java new file mode 100644 index 0000000000..f24a3adf1f --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java @@ -0,0 +1,72 @@ +/* + * 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.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.io.BaseEncoding.base16; +import static com.google.common.net.HttpHeaders.ETAG; +import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT; + +import java.util.Date; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequest.Builder; +import org.jclouds.io.Payload; +import org.jclouds.rest.Binder; + +import com.google.common.hash.HashCode; + +public class SetPayload implements Binder { + + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + Builder builder = request.toBuilder(); + Payload payload = Payload.class.cast(input); + + if (payload.getContentMetadata().getContentType() == null) { + // TODO: use `X-Detect-Content-Type` here. Should be configurable via a property. + payload.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM); + } + + Long contentLength = payload.getContentMetadata().getContentLength(); + if (contentLength != null && contentLength >= 0) { + checkArgument(contentLength <= 5l * 1024 * 1024 * 1024, "maximum size for put object is 5GB, %s", + contentLength); + } else { + builder.replaceHeader(TRANSFER_ENCODING, "chunked").build(); + } + + HashCode md5 = payload.getContentMetadata().getContentMD5AsHashCode(); + if (md5 != null) { + // Swift will validate the md5, if placed as an ETag header + builder.replaceHeader(ETAG, base16().lowerCase().encode(md5.asBytes())); + } + + Date expires = payload.getContentMetadata().getExpires(); + if (expires != null) { + builder.addHeader(OBJECT_DELETE_AT, + String.valueOf(MILLISECONDS.toSeconds(expires.getTime()))).build(); + } + + return (R) builder.payload(payload).build(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java new file mode 100644 index 0000000000..e58df8993c --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java @@ -0,0 +1,154 @@ +/* + * 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.blobstore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.Context; +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.attr.ConsistencyModel; +import org.jclouds.internal.BaseView; +import org.jclouds.location.Provider; +import org.jclouds.location.Region; +import org.jclouds.rest.Utils; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.reflect.TypeToken; + +/** + * Implementation of {@link BlobStoreContext} which allows you to employ + * multiple regions. + * + * Example. + * + *
+ * ctx = contextBuilder.buildView(RegionScopedBlobStoreContext.class);
+ *
+ * Set<String> regionIds = ctx.getConfiguredRegions();
+ *
+ * // isolated to a specific region
+ * BlobStore texasBlobStore = ctx.getBlobStore("US-TX");
+ * BlobStore virginiaBlobStore = ctx.getBlobStore("US-VA");
+ * 
+ */ +public class RegionScopedBlobStoreContext extends BaseView implements BlobStoreContext { + + /** + * @return regions supported in this context. + */ + public Set getConfiguredRegions() { + return regionIds.get(); + } + + /** + * @param regionId + * valid region id from {@link #getConfiguredRegions()} + * @throws IllegalArgumentException + * if {@code regionId} was invalid. + */ + public BlobStore getBlobStore(String regionId) { + checkRegionId(regionId); + return blobStore.apply(regionId); + } + + /** + * @param regionId + * valid region id from {@link #getConfiguredRegions()} + * @throws IllegalArgumentException + * if {@code regionId} was invalid. + */ + public BlobRequestSigner getSigner(String regionId) { + checkRegionId(regionId); + return blobRequestSigner.apply(regionId); + } + + protected void checkRegionId(String regionId) { + checkArgument(getConfiguredRegions().contains(checkNotNull(regionId, "regionId was null")), "region %s not in %s", + regionId, getConfiguredRegions()); + } + + private final Supplier> regionIds; + private final Supplier implicitRegionId; + // factory functions are decoupled so that you can exchange how requests are + // signed or decorate without a class hierarchy dependency + private final Function blobStore; + private final Function blobRequestSigner; + private final Utils utils; + private final ConsistencyModel consistencyModel; + + @Inject + public RegionScopedBlobStoreContext(@Provider Context backend, @Provider TypeToken backendType, + @Region Supplier> regionIds, @Region Supplier implicitRegionId, + Function blobStore, Function blobRequestSigner, Utils utils, + ConsistencyModel consistencyModel) { + super(backend, backendType); + this.regionIds = checkNotNull(regionIds, "regionIds"); + this.implicitRegionId = checkNotNull(implicitRegionId, "implicitRegionId"); + this.blobStore = checkNotNull(blobStore, "blobStore"); + this.blobRequestSigner = checkNotNull(blobRequestSigner, "blobRequestSigner"); + this.utils = checkNotNull(utils, "utils"); + this.consistencyModel = checkNotNull(consistencyModel, "consistencyModel"); + } + + @Override + public ConsistencyModel getConsistencyModel() { + return consistencyModel; + } + + @Override + public BlobStore getBlobStore() { + return getBlobStore(implicitRegionId.get()); + } + + @Override + public BlobRequestSigner getSigner() { + return getSigner(implicitRegionId.get()); + } + + @Override + public Utils utils() { + return utils; + } + + @Override + public void close() { + delegate().close(); + } + + public int hashCode() { + return delegate().hashCode(); + } + + @Override + public String toString() { + return delegate().toString(); + } + + @Override + public boolean equals(Object obj) { + return delegate().equals(obj); + } + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java new file mode 100644 index 0000000000..c0f65c5457 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java @@ -0,0 +1,312 @@ +/* + * 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.blobstore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.tryFind; +import static com.google.common.collect.Lists.transform; +import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive; +import static org.jclouds.location.predicates.LocationPredicates.idEquals; +import static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata; + +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.domain.BlobBuilder; +import org.jclouds.blobstore.domain.BlobMetadata; +import org.jclouds.blobstore.domain.MutableBlobMetadata; +import org.jclouds.blobstore.domain.PageSet; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.blobstore.domain.StorageType; +import org.jclouds.blobstore.domain.internal.BlobBuilderImpl; +import org.jclouds.blobstore.domain.internal.BlobImpl; +import org.jclouds.blobstore.domain.internal.PageSetImpl; +import org.jclouds.blobstore.functions.BlobToHttpGetOptions; +import org.jclouds.blobstore.options.CreateContainerOptions; +import org.jclouds.blobstore.options.GetOptions; +import org.jclouds.blobstore.options.ListContainerOptions; +import org.jclouds.blobstore.options.PutOptions; +import org.jclouds.blobstore.strategy.ClearListStrategy; +import org.jclouds.collect.Memoized; +import org.jclouds.domain.Location; +import org.jclouds.io.Payload; +import org.jclouds.io.payloads.ByteSourcePayload; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.blobstore.functions.ToBlobMetadata; +import org.jclouds.openstack.swift.v1.blobstore.functions.ToListContainerOptions; +import org.jclouds.openstack.swift.v1.blobstore.functions.ToResourceMetadata; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.features.ObjectApi; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.ByteSource; +import com.google.inject.AbstractModule; +import com.google.inject.Injector; +import com.google.inject.assistedinject.Assisted; + +public class RegionScopedSwiftBlobStore implements BlobStore { + + @Inject + protected RegionScopedSwiftBlobStore(Injector baseGraph, BlobStoreContext context, SwiftApi api, + @Memoized Supplier> locations, @Assisted String regionId) { + checkNotNull(regionId, "regionId"); + Optional found = tryFind(locations.get(), idEquals(regionId)); + checkArgument(found.isPresent(), "region %s not in %s", regionId, locations.get()); + this.region = found.get(); + this.regionId = regionId; + this.toResourceMetadata = new ToResourceMetadata(found.get()); + this.context = context; + this.api = api; + // until we parameterize ClearListStrategy with a factory + this.clearList = baseGraph.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(BlobStore.class).toInstance(RegionScopedSwiftBlobStore.this); + } + }).getInstance(ClearListStrategy.class); + } + + private final BlobStoreContext context; + private final ClearListStrategy clearList; + private final SwiftApi api; + private final Location region; + private final String regionId; + private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); + private final ToListContainerOptions toListContainerOptions = new ToListContainerOptions(); + private final ToResourceMetadata toResourceMetadata; + + @Override + public Set listAssignableLocations() { + return ImmutableSet.of(region); + } + + @Override + public PageSet list() { + // TODO: there may eventually be >10k containers.. + FluentIterable containers = api.getContainerApi(regionId).list() + .transform(toResourceMetadata); + return new PageSetImpl(containers, null); + } + + @Override + public boolean containerExists(String container) { + Container val = api.getContainerApi(regionId).get(container); + containerCache.put(container, Optional.fromNullable(val)); + return val != null; + } + + @Override + public boolean createContainerInLocation(Location location, String container) { + return createContainerInLocation(location, container, CreateContainerOptions.NONE); + } + + @Override + public boolean createContainerInLocation(Location location, String container, CreateContainerOptions options) { + checkArgument(location == null || location.equals(region), "location must be null or %s", region); + if (options.isPublicRead()) { + return api.getContainerApi(regionId).create(container, ANYBODY_READ); + } + return api.getContainerApi(regionId).create(container, BASIC_CONTAINER); + } + + private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions BASIC_CONTAINER = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions(); + private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions ANYBODY_READ = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions() + .anybodyRead(); + + @Override + public PageSet list(String container) { + return list(container, ListContainerOptions.NONE); + } + + @Override + public PageSet list(final String container, ListContainerOptions options) { + ObjectApi objectApi = api.getObjectApi(regionId, container); + ObjectList objects = objectApi.list(toListContainerOptions.apply(options)); + if (objects == null) { + containerCache.put(container, Optional. absent()); + return new PageSetImpl(ImmutableList. of(), null); + } else { + containerCache.put(container, Optional.of(objects.getContainer())); + List list = transform(objects, toBlobMetadata(container)); + int limit = Optional.fromNullable(options.getMaxResults()).or(10000); + String marker = list.size() == limit ? list.get(limit - 1).getName() : null; + // TODO: we should probably deprecate this option + if (options.isDetailed()) { + list = transform(list, new Function() { + @Override + public StorageMetadata apply(StorageMetadata input) { + if (input.getType() != StorageType.BLOB) { + return input; + } + return blobMetadata(container, input.getName()); + } + }); + } + return new PageSetImpl(list, marker); + } + } + + @Override + public boolean blobExists(String container, String name) { + return blobMetadata(container, name) != null; + } + + @Override + public String putBlob(String container, Blob blob) { + return putBlob(container, blob, PutOptions.NONE); + } + + @Override + public String putBlob(String container, Blob blob, PutOptions options) { + if (options.isMultipart()) { + throw new UnsupportedOperationException(); + } + ObjectApi objectApi = api.getObjectApi(regionId, container); + return objectApi.put(blob.getMetadata().getName(), blob.getPayload(), metadata(blob.getMetadata().getUserMetadata())); + } + + @Override + public BlobMetadata blobMetadata(String container, String name) { + SwiftObject object = api.getObjectApi(regionId, container).get(name); + if (object == null) { + return null; + } + return toBlobMetadata(container).apply(object); + } + + @Override + public Blob getBlob(String container, String key) { + return getBlob(container, key, GetOptions.NONE); + } + + @Override + public Blob getBlob(String container, String name, GetOptions options) { + ObjectApi objectApi = api.getObjectApi(regionId, container); + SwiftObject object = objectApi.get(name, toGetOptions.apply(options)); + if (object == null) { + return null; + } + Blob blob = new BlobImpl(toBlobMetadata(container).apply(object)); + blob.setPayload(object.getPayload()); + blob.setAllHeaders(object.getHeaders()); + return blob; + } + + @Override + public void removeBlob(String container, String name) { + api.getObjectApi(regionId, container).delete(name); + } + + @Override + public BlobStoreContext getContext() { + return context; + } + + @Override + public BlobBuilder blobBuilder(String name) { + return new BlobBuilderImpl().name(name); + } + + @Override + public boolean directoryExists(String containerName, String directory) { + return api.getObjectApi(regionId, containerName) + .get(directory) != null; + } + + @Override + public void createDirectory(String containerName, String directory) { + api.getObjectApi(regionId, containerName) + .put(directory, directoryPayload); + } + + private final Payload directoryPayload = new ByteSourcePayload(ByteSource.wrap(new byte[] {})) { + { + getContentMetadata().setContentType("application/directory"); + } + }; + + @Override + public void deleteDirectory(String containerName, String directory) { + api.getObjectApi(regionId, containerName).delete(directory); + } + + @Override + public long countBlobs(String containerName) { + Container container = api.getContainerApi(regionId).get(containerName); + // undefined if container doesn't exist, so default to zero + return container != null ? container.getObjectCount() : 0; + } + + @Override + public void clearContainer(String containerName) { + clearContainer(containerName, recursive()); + } + + @Override + public void clearContainer(String containerName, ListContainerOptions options) { + // this could be implemented to use bulk delete + clearList.execute(containerName, options); + } + + @Override + public void deleteContainer(String container) { + clearContainer(container, recursive()); + api.getContainerApi(regionId).deleteIfEmpty(container); + containerCache.invalidate(container); + } + + @Override + public boolean deleteContainerIfEmpty(String container) { + boolean deleted = api.getContainerApi(regionId).deleteIfEmpty(container); + if (deleted) { + containerCache.invalidate(container); + } + return deleted; + } + + protected final LoadingCache> containerCache = CacheBuilder.newBuilder().build( + new CacheLoader>() { + public Optional load(String container) { + return Optional.fromNullable(api.getContainerApi(regionId).get(container)); + } + }); + + protected Function toBlobMetadata(String container) { + return new ToBlobMetadata(containerCache.getUnchecked(container).get()); + } + + @Override + public long countBlobs(String containerName, ListContainerOptions options) { + throw new UnsupportedOperationException(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java new file mode 100644 index 0000000000..5134efae34 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java @@ -0,0 +1,109 @@ +/* + * 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.blobstore; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.inject.Provider; + +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.functions.BlobToHttpGetOptions; +import org.jclouds.date.TimeStamp; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.Uris; +import org.jclouds.http.options.GetOptions; +import org.jclouds.location.Region; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.TemporaryUrlSigner; + +import com.google.common.base.Supplier; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.name.Named; + +/** + * Uses {@link TemporaryUrlSigner} to sign requests for access to blobs. If no + * interval is supplied, it defaults to a year. + */ +public class RegionScopedTemporaryUrlBlobSigner implements BlobRequestSigner { + + @Inject + protected RegionScopedTemporaryUrlBlobSigner(@Region Supplier>> regionToUris, + @Named(PROPERTY_SESSION_INTERVAL) long seconds, @TimeStamp Provider timestamp, SwiftApi api, + @Assisted String regionId) { + checkNotNull(regionId, "regionId"); + this.timestamp = timestamp; + this.signer = TemporaryUrlSigner.checkApiEvery(api.getAccountApi(regionId), seconds); + this.storageUrl = regionToUris.get().get(regionId).get(); + } + + private static final long YEAR = TimeUnit.DAYS.toSeconds(365); + private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions(); + private final Provider timestamp; + private final TemporaryUrlSigner signer; + private final URI storageUrl; + + @Override + public HttpRequest signGetBlob(String container, String name) { + return signGetBlob(container, name, YEAR); + } + + @Override + public HttpRequest signGetBlob(String container, String name, long timeInSeconds) { + return sign("GET", container, name, GetOptions.NONE, timestamp.get() + timeInSeconds); + } + + @Override + public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) { + return sign("GET", container, name, toGetOptions.apply(options), timestamp.get() + YEAR); + } + + @Override + public HttpRequest signPutBlob(String container, Blob blob) { + return signPutBlob(container, blob, YEAR); + } + + @Override + public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) { + return sign("PUT", container, blob.getMetadata().getName(), GetOptions.NONE, timestamp.get() + timeInSeconds); + } + + @Override + public HttpRequest signRemoveBlob(String container, String name) { + return sign("DELETE", container, name, GetOptions.NONE, timestamp.get() + YEAR); + } + + private HttpRequest sign(String method, String container, String name, GetOptions options, long expires) { + checkNotNull(container, "container"); + checkNotNull(name, "name"); + URI url = Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build(); + String signature = signer.sign(method, url.getPath(), expires); + return HttpRequest.builder() + .method(method) + .endpoint(url) + .addQueryParams(options.buildQueryParameters()) + .addQueryParam("temp_url_sig", signature) + .addQueryParam("temp_url_expires", String.valueOf(expires)) + .headers(options.buildRequestHeaders()).build(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java new file mode 100644 index 0000000000..43a876da4e --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java @@ -0,0 +1,67 @@ +/* + * 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.blobstore.config; + +import javax.inject.Inject; + +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.date.TimeStamp; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedTemporaryUrlBlobSigner; + +import com.google.common.base.Function; +import com.google.common.collect.ForwardingObject; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.assistedinject.FactoryModuleBuilder; + +public class SignUsingTemporaryUrls extends AbstractModule { + + @Override + protected void configure() { + install(new FactoryModuleBuilder().build(Factory.class)); + } + + interface Factory { + RegionScopedTemporaryUrlBlobSigner create(String in); + } + + @Provides + Function blobRequestSigner(FactoryFunction in) { + return in; + } + + static class FactoryFunction extends ForwardingObject implements Function { + @Inject + Factory delegate; + + @Override + protected Factory delegate() { + return delegate; + } + + @Override + public BlobRequestSigner apply(String in) { + return delegate.create(in); + } + } + + @Provides + @TimeStamp + protected Long unixEpochTimestamp() { + return System.currentTimeMillis() / 1000; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java new file mode 100644 index 0000000000..c3b0a82ec4 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java @@ -0,0 +1,65 @@ +/* + * 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.blobstore.config; + +import javax.inject.Inject; + +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.attr.ConsistencyModel; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext; +import org.jclouds.openstack.swift.v1.blobstore.RegionScopedSwiftBlobStore; + +import com.google.common.base.Function; +import com.google.common.collect.ForwardingObject; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.assistedinject.FactoryModuleBuilder; + +public class SwiftBlobStoreContextModule extends AbstractModule { + + @Override + protected void configure() { + bind(ConsistencyModel.class).toInstance(ConsistencyModel.EVENTUAL); + bind(BlobStoreContext.class).to(RegionScopedBlobStoreContext.class); + install(new FactoryModuleBuilder().build(Factory.class)); + } + + interface Factory { + RegionScopedSwiftBlobStore create(String in); + } + + @Provides + Function blobStore(FactoryFunction in) { + return in; + } + + static class FactoryFunction extends ForwardingObject implements Function { + @Inject + Factory delegate; + + @Override + protected Factory delegate() { + return delegate; + } + + @Override + public BlobStore apply(String in) { + return delegate.create(in); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java new file mode 100644 index 0000000000..6a1b8c5de1 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java @@ -0,0 +1,71 @@ +/* + * 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.blobstore.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.blobstore.domain.MutableBlobMetadata; +import org.jclouds.blobstore.domain.StorageType; +import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl; +import org.jclouds.blobstore.strategy.IfDirectoryReturnNameStrategy; +import org.jclouds.blobstore.strategy.internal.MarkersIfDirectoryReturnNameStrategy; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; + +import com.google.common.base.Function; + +public class ToBlobMetadata implements Function { + + private final IfDirectoryReturnNameStrategy ifDirectoryReturnName = new MarkersIfDirectoryReturnNameStrategy(); + private final Container container; + + public ToBlobMetadata(Container container) { + this.container = checkNotNull(container, "container"); + } + + @Override + public MutableBlobMetadata apply(SwiftObject from) { + if (from == null) + return null; + MutableBlobMetadata to = new MutableBlobMetadataImpl(); + to.setContainer(container.getName()); + if (container.getAnybodyRead().isPresent()) { + to.setPublicUri(from.getUri()); + } + to.setUri(from.getUri()); + to.setETag(from.getETag()); + to.setName(from.getName()); + to.setLastModified(from.getLastModified()); + to.setContentMetadata(from.getPayload().getContentMetadata()); + to.getContentMetadata().setContentMD5(from.getPayload().getContentMetadata().getContentMD5AsHashCode()); + to.getContentMetadata().setExpires(from.getPayload().getContentMetadata().getExpires()); + to.setUserMetadata(from.getMetadata()); + String directoryName = ifDirectoryReturnName.execute(to); + if (directoryName != null) { + to.setName(directoryName); + to.setType(StorageType.RELATIVE_PATH); + } else { + to.setType(StorageType.BLOB); + } + return to; + } + + @Override + public String toString() { + return "ObjectToBlobMetadata(" + container + ")"; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java new file mode 100644 index 0000000000..2650f5b389 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java @@ -0,0 +1,52 @@ +/* + * 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.blobstore.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.blobstore.options.ListContainerOptions; + +import com.google.common.base.Function; + +public class ToListContainerOptions implements + Function { + + @Override + public org.jclouds.openstack.swift.v1.options.ListContainerOptions apply(ListContainerOptions from) { + checkNotNull(from, "set options to instance NONE instead of passing null"); + org.jclouds.openstack.swift.v1.options.ListContainerOptions options = new org.jclouds.openstack.swift.v1.options.ListContainerOptions(); + if ((from.getDir() == null) && (from.isRecursive())) { + options.prefix(""); + } + if ((from.getDir() == null) && (!from.isRecursive())) { + options.path(""); + } + if ((from.getDir() != null) && (from.isRecursive())) { + options.prefix(from.getDir().endsWith("/") ? from.getDir() : from.getDir() + "/"); + } + if ((from.getDir() != null) && (!from.isRecursive())) { + options.path(from.getDir()); + } + if (from.getMarker() != null) { + options.marker(from.getMarker()); + } + if (from.getMaxResults() != null) { + options.limit(from.getMaxResults()); + } + return options; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java new file mode 100644 index 0000000000..bc5e1868c6 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java @@ -0,0 +1,44 @@ +/* + * 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.blobstore.functions; + +import org.jclouds.blobstore.domain.MutableStorageMetadata; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.blobstore.domain.StorageType; +import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl; +import org.jclouds.domain.Location; +import org.jclouds.openstack.swift.v1.domain.Container; + +import com.google.common.base.Function; + +public class ToResourceMetadata implements Function { + private Location region; + + public ToResourceMetadata(Location region) { + this.region = region; + } + + @Override + public StorageMetadata apply(Container from) { + MutableStorageMetadata to = new MutableStorageMetadataImpl(); + to.setName(from.getName()); + to.setLocation(region); + to.setType(StorageType.CONTAINER); + to.setUserMetadata(from.getMetadata()); + return to; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java new file mode 100644 index 0000000000..d6d8b6a243 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.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.config; + +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.handlers.SwiftErrorHandler; +import org.jclouds.rest.ConfiguresHttpApi; +import org.jclouds.rest.config.HttpApiModule; + +@ConfiguresHttpApi +public abstract class BaseSwiftHttpApiModule extends HttpApiModule { + + protected BaseSwiftHttpApiModule(Class api) { + super(api); + } + + @Override + protected void configure() { + super.configure(); + } + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(SwiftErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(SwiftErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(SwiftErrorHandler.class); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java new file mode 100644 index 0000000000..cb93760588 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java @@ -0,0 +1,32 @@ +/* + * 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.config; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.rest.ConfiguresHttpApi; + +@ConfiguresHttpApi +public class SwiftHttpApiModule extends BaseSwiftHttpApiModule { + + public SwiftHttpApiModule() { + super(SwiftApi.class); + } + + @Override + protected void configure() { + super.configure(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java new file mode 100644 index 0000000000..4eafdb697c --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java @@ -0,0 +1,119 @@ +/* + * 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.config; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.Map; + +import org.jclouds.json.config.GsonModule.DateAdapter; +import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; +import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse; +import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; + +public class SwiftTypeAdapters extends AbstractModule { + + @Override + protected void configure() { + bind(DateAdapter.class).to(Iso8601DateAdapter.class); + } + + @Provides + public Map provideCustomAdapterBindings() { + return ImmutableMap. builder() + .put(ExtractArchiveResponse.class, new ExtractArchiveResponseAdapter()) + .put(BulkDeleteResponse.class, new BulkDeleteResponseAdapter()).build(); + } + + static class ExtractArchiveResponseAdapter extends TypeAdapter { + + @Override + public ExtractArchiveResponse read(JsonReader reader) throws IOException { + int created = 0; + Builder errors = ImmutableMap. builder(); + reader.beginObject(); + while (reader.hasNext()) { + String key = reader.nextName(); + if (key.equals("Number Files Created")) { + created = reader.nextInt(); + } else if (key.equals("Errors")) { + readErrors(reader, errors); + } else { + reader.skipValue(); + } + } + reader.endObject(); + return ExtractArchiveResponse.create(created, errors.build()); + } + + @Override + public void write(JsonWriter arg0, ExtractArchiveResponse arg1) throws IOException { + throw new UnsupportedOperationException(); + } + } + + static class BulkDeleteResponseAdapter extends TypeAdapter { + + @Override + public BulkDeleteResponse read(JsonReader reader) throws IOException { + int deleted = 0; + int notFound = 0; + Builder errors = ImmutableMap. builder(); + reader.beginObject(); + while (reader.hasNext()) { + String key = reader.nextName(); + if (key.equals("Number Deleted")) { + deleted = reader.nextInt(); + } else if (key.equals("Number Not Found")) { + notFound = reader.nextInt(); + } else if (key.equals("Errors")) { + readErrors(reader, errors); + } else { + reader.skipValue(); + } + } + reader.endObject(); + return BulkDeleteResponse.create(deleted, notFound, errors.build()); + } + + @Override + public void write(JsonWriter arg0, BulkDeleteResponse arg1) throws IOException { + throw new UnsupportedOperationException(); + } + } + + static void readErrors(JsonReader reader, Builder errors) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + reader.beginArray(); + String decodedPath = URI.create(reader.nextString()).getPath(); + errors.put(decodedPath, reader.nextString()); + reader.endArray(); + } + reader.endArray(); + } + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java new file mode 100644 index 0000000000..b52907ca1b --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java @@ -0,0 +1,219 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +/** + * Represents an Account in OpenStack Object Storage. + * + * @see org.jclouds.openstack.swift.v1.features.AccountApi + */ +public class Account { + + private final long containerCount; + private final long objectCount; + private final long bytesUsed; + private final Map metadata; + private final Multimap headers; + + // parsed from headers, so ConstructorProperties here would be misleading + protected Account(long containerCount, long objectCount, long bytesUsed, Map metadata, + Multimap headers) { + this.containerCount = containerCount; + this.objectCount = objectCount; + this.bytesUsed = bytesUsed; + this.metadata = metadata == null ? ImmutableMap. of() : metadata; + this.headers = headers == null ? ImmutableMultimap. of() : headers; + } + + /** + * @return The count of containers for this account. + */ + public long getContainerCount() { + return containerCount; + } + + /** + * @return The count of objects for this account. + */ + public long getObjectCount() { + return objectCount; + } + + /** + * @return The number of bytes used by this account. + */ + public long getBytesUsed() { + return bytesUsed; + } + + /** + * @return The {@link Optional<String>} temporary URL key for this account. + */ + public Optional getTemporaryUrlKey() { + return Optional.fromNullable(metadata.get("temp-url-key")); + } + + /** + *

NOTE

+ * In current swift implementations, headers keys are lower-cased. This means + * characters such as turkish will probably not work out well. + * + * @return a {@code Map} containing the account metadata. + */ + public Map getMetadata() { + return metadata; + } + + /** + * @return The HTTP headers for this account. + */ + public Multimap getHeaders() { + return headers; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Account) { + Account that = Account.class.cast(object); + return equal(getContainerCount(), that.getContainerCount()) + && equal(getObjectCount(), that.getObjectCount()) + && equal(getBytesUsed(), that.getBytesUsed()) + && equal(getMetadata(), that.getMetadata()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getContainerCount(), getObjectCount(), getBytesUsed(), getMetadata()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper(this) + .add("containerCount", getContainerCount()) + .add("objectCount", getObjectCount()) + .add("bytesUsed", getBytesUsed()) + .add("metadata", getMetadata()); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromAccount(this); + } + + public static class Builder { + protected long containerCount; + protected long objectCount; + protected long bytesUsed; + protected Multimap headers = ImmutableMultimap.of(); + protected Map metadata = ImmutableMap.of(); + + /** + * @param containerCount the count of containers for this account. + * + * @see Account#getContainerCount() + */ + public Builder containerCount(long containerCount) { + this.containerCount = containerCount; + return this; + } + + /** + * @param objectCount the count of objects for this account. + * + * @see Account#getObjectCount() + */ + public Builder objectCount(long objectCount) { + this.objectCount = objectCount; + return this; + } + + /** + * @param bytesUsed the number of bytes used by this account. + * + * @see Account#getBytesUsed() + */ + public Builder bytesUsed(long bytesUsed) { + this.bytesUsed = bytesUsed; + return this; + } + + /** + *

NOTE

+ * This method will lower-case all metadata keys due to a Swift implementation + * decision. + * + * @param metadata the metadata for this account. + * + * @see Account#getMetadata() + */ + public Builder metadata(Map metadata) { + ImmutableMap.Builder builder = ImmutableMap. builder(); + for (Entry entry : checkNotNull(metadata, "metadata").entrySet()) { + builder.put(entry.getKey().toLowerCase(), entry.getValue()); + } + this.metadata = builder.build(); + return this; + } + + /** + * @see Account#getHeaders() + */ + public Builder headers(Multimap headers) { + this.headers = headers; + return this; + } + + public Account build() { + return new Account(containerCount, objectCount, bytesUsed, metadata, headers); + } + + public Builder fromAccount(Account from) { + return containerCount(from.getContainerCount()) + .objectCount(from.getObjectCount()) + .bytesUsed(from.getBytesUsed()) + .metadata(from.getMetadata()) + .headers(from.getHeaders()); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java new file mode 100644 index 0000000000..4f7904467e --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java @@ -0,0 +1,101 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; + +/** + * Represents a response from a Bulk Delete request. + * + * @see org.jclouds.openstack.swift.v1.features.BulkApi + */ +public class BulkDeleteResponse { + public static BulkDeleteResponse create(int deleted, int notFound, Map errors) { + return new BulkDeleteResponse(deleted, notFound, errors); + } + + private final int deleted; + private final int notFound; + private final Map errors; + + private BulkDeleteResponse(int deleted, int notFound, Map errors) { + this.deleted = deleted; + this.notFound = notFound; + this.errors = checkNotNull(errors, "errors"); + } + + /** + * @return The number of files deleted. + * */ + public int getDeleted() { + return deleted; + } + + /** + * @return The number of files not found. + */ + public int getNotFound() { + return notFound; + } + + /** + * @return a {@code Map} containing each path that failed + * to be deleted and its corresponding error response. + */ + public Map getErrors() { + return errors; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof BulkDeleteResponse) { + BulkDeleteResponse that = BulkDeleteResponse.class.cast(object); + return equal(getDeleted(), that.getDeleted()) + && equal(getNotFound(), that.getNotFound()) + && equal(getErrors(), that.getErrors()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getDeleted(), getNotFound(), getErrors()); + } + + protected ToStringHelper string() { + return toStringHelper(this) + .add("deleted", getDeleted()) + .add("notFound", getNotFound()) + .add("errors", getErrors()); + } + + @Override + public String toString() { + return string().toString(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java new file mode 100644 index 0000000000..4a31ab7ae8 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java @@ -0,0 +1,237 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +/** + * Represents a Container in OpenStack Object Storage. + * + * @see org.jclouds.openstack.swift.v1.features.ContainerApi + */ +public class Container implements Comparable { + + private final String name; + private final long objectCount; + private final long bytesUsed; + private final Optional anybodyRead; + private final Map metadata; + private final Multimap headers; + + @ConstructorProperties({ "name", "count", "bytes", "anybodyRead", "metadata", "headers"}) + protected Container(String name, long objectCount, long bytesUsed, Optional anybodyRead, + Map metadata, Multimap headers) { + this.name = checkNotNull(name, "name"); + this.objectCount = objectCount; + this.bytesUsed = bytesUsed; + this.anybodyRead = anybodyRead == null ? Optional. absent() : anybodyRead; + this.metadata = metadata == null ? ImmutableMap. of() : metadata; + this.headers = headers == null ? ImmutableMultimap. of() : headers; + } + + /** + * @return The name of this container. + */ + public String getName() { + return name; + } + + /** + * @return The count of objects for this container. + */ + public long getObjectCount() { + return objectCount; + } + + /** + * @return The number of bytes used by this container. + */ + public long getBytesUsed() { + return bytesUsed; + } + + /** + * Absent except in {@link ContainerApi#get(String) Get Container} commands. + * + * @return true if this container is publicly readable, false otherwise. + * + * @see org.jclouds.openstack.swift.v1.options.CreateContainerOptions#anybodyRead() + */ + public Optional getAnybodyRead() { + return anybodyRead; + } + + /** + *

NOTE

+ * In current swift implementations, headers keys are lower-cased. This means + * characters such as turkish will probably not work out well. + * + * @return a {@code Map} containing this container's metadata. + */ + public Map getMetadata() { + return metadata; + } + + /** + * @return The HTTP headers for this account. + */ + public Multimap getHeaders() { + return headers; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Container) { + final Container that = Container.class.cast(object); + return equal(getName(), that.getName()) + && equal(getObjectCount(), that.getObjectCount()) + && equal(getBytesUsed(), that.getBytesUsed()) + && equal(getMetadata(), that.getMetadata()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getName(), getObjectCount(), getBytesUsed(), getAnybodyRead(), getMetadata()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper(this).omitNullValues() + .add("name", getName()) + .add("objectCount", getObjectCount()) + .add("bytesUsed", getBytesUsed()) + .add("anybodyRead", getAnybodyRead().orNull()) + .add("metadata", getMetadata()); + } + + @Override + public int compareTo(Container that) { + if (that == null) + return 1; + if (this == that) + return 0; + return this.getName().compareTo(that.getName()); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromContainer(this); + } + + public static class Builder { + protected String name; + protected long objectCount; + protected long bytesUsed; + protected Optional anybodyRead = Optional.absent(); + protected Map metadata = ImmutableMap.of(); + protected Multimap headers = ImmutableMultimap.of(); + + /** + * @see Container#getName() + */ + public Builder name(String name) { + this.name = checkNotNull(name, "name"); + return this; + } + + /** + * @see Container#getObjectCount() + */ + public Builder objectCount(long objectCount) { + this.objectCount = objectCount; + return this; + } + + /** + * @see Container#getBytesUsed() + */ + public Builder bytesUsed(long bytesUsed) { + this.bytesUsed = bytesUsed; + return this; + } + + /** + * @see Container#getAnybodyRead() + */ + public Builder anybodyRead(Boolean anybodyRead) { + this.anybodyRead = Optional.fromNullable(anybodyRead); + return this; + } + + /** + *

NOTE

+ * This method will lower-case all metadata keys. + * + * @see Container#getMetadata() + */ + public Builder metadata(Map metadata) { + ImmutableMap.Builder builder = ImmutableMap. builder(); + for (Entry entry : checkNotNull(metadata, "metadata").entrySet()) { + builder.put(entry.getKey().toLowerCase(), entry.getValue()); + } + this.metadata = builder.build(); + return this; + } + + /** + * @see Container#getHeaders() + */ + public Builder headers(Multimap headers) { + this.headers = headers; + return this; + } + + public Container build() { + return new Container(name, objectCount, bytesUsed, anybodyRead, metadata, headers); + } + + public Builder fromContainer(Container from) { + return name(from.getName()) + .objectCount(from.getObjectCount()) + .bytesUsed(from.getBytesUsed()) + .anybodyRead(from.getAnybodyRead().orNull()) + .metadata(from.getMetadata()) + .headers(from.getHeaders()); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java new file mode 100644 index 0000000000..7e06e0eb1f --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java @@ -0,0 +1,90 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; + +/** + * Represents a response from an Extract Archive request. + * + * @see org.jclouds.openstack.swift.v1.features.BulkApi + */ +public class ExtractArchiveResponse { + public static ExtractArchiveResponse create(int created, Map errors) { + return new ExtractArchiveResponse(created, errors); + } + + private final int created; + private final Map errors; + + private ExtractArchiveResponse(int created, Map errors) { + this.created = created; + this.errors = checkNotNull(errors, "errors"); + } + + /** + * @return The number of files created. + */ + public int getCreated() { + return created; + } + + /** + * @return a {@code Map} containing each path that failed + * to be created and its corresponding error response. + */ + public Map getErrors() { + return errors; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof ExtractArchiveResponse) { + ExtractArchiveResponse that = ExtractArchiveResponse.class.cast(object); + return equal(getCreated(), that.getCreated()) + && equal(getErrors(), that.getErrors()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getCreated(), getErrors()); + } + + protected ToStringHelper string() { + return toStringHelper(this) + .add("created", getCreated()) + .add("errors", getErrors()); + } + + @Override + public String toString() { + return string().toString(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java new file mode 100644 index 0000000000..5bc3136fb5 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java @@ -0,0 +1,57 @@ +/* + * 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.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; + +import com.google.common.collect.ForwardingList; + +/** + * Represents a list of objects in a container. + * + * @see Container + * @see SwiftObject + * @see org.jclouds.openstack.swift.v1.features.ObjectApi#list() + */ +public class ObjectList extends ForwardingList { + + public static ObjectList create(List objects, Container container) { + return new ObjectList(objects, container); + } + + private final List objects; + private final Container container; + + protected ObjectList(List objects, Container container) { + this.objects = checkNotNull(objects, "objects"); + this.container = checkNotNull(container, "container"); + } + + /** + * @return the parent {@link Container} the objects reside in. + */ + public Container getContainer() { + return container; + } + + @Override + protected List delegate() { + return objects; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java new file mode 100644 index 0000000000..a5f064f665 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java @@ -0,0 +1,136 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Named; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; + +/** + * Represents a single segment of a multi-part upload. + * + * @see org.jclouds.openstack.swift.v1.features.StaticLargeObjectApi + */ +public class Segment { + + private final String path; + private final String etag; + @Named("size_bytes") + private final long sizeBytes; + + private Segment(String path, String etag, long sizeBytes) { + this.path = checkNotNull(path, "path"); + this.etag = checkNotNull(etag, "etag of %s", path); + this.sizeBytes = checkNotNull(sizeBytes, "sizeBytes of %s", path); + } + + /** + * @return The container and object name in the format: {@code /} + */ + public String getPath() { + return path; + } + + /** + * @return The ETag of the content of the segment object. + */ + public String getETag() { + return etag; + } + + /** + * @return The size of the segment object. + */ + public long getSizeBytes() { + return sizeBytes; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Segment) { + Segment that = Segment.class.cast(object); + return equal(getPath(), that.getPath()) + && equal(getETag(), that.getETag()) + && equal(getSizeBytes(), that.getSizeBytes()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getPath(), getETag(), getSizeBytes()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper(this) + .add("path", getPath()) + .add("etag", getETag()) + .add("sizeBytes", getSizeBytes()); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + protected String path; + protected String etag; + protected long sizeBytes; + + /** + * @see Segment#getPath() + */ + public Builder path(String path) { + this.path = path; + return this; + } + + /** + * @see Segment#getEtag() + */ + public Builder etag(String etag) { + this.etag = etag; + return this; + } + + /** + * @see Segment#getSizeBytes() + */ + public Builder sizeBytes(long sizeBytes) { + this.sizeBytes = sizeBytes; + return this; + } + + public Segment build() { + return new Segment(path, etag, sizeBytes); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java new file mode 100644 index 0000000000..2184ff2dea --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java @@ -0,0 +1,267 @@ +/* + * 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.domain; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URI; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.io.Payload; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +/** + * Represents an object in OpenStack Object Storage. + * + * + * @see ObjectApi + */ +public class SwiftObject implements Comparable { + + private final String name; + private final URI uri; + private final String etag; + private final Date lastModified; + private final Multimap headers; + private final Map metadata; + private final Payload payload; + + protected SwiftObject(String name, URI uri, String etag, Date lastModified, + Multimap headers, Map metadata, Payload payload) { + this.name = checkNotNull(name, "name"); + this.uri = checkNotNull(uri, "uri of %s", uri); + this.etag = checkNotNull(etag, "etag of %s", name).replace("\"", ""); + this.lastModified = checkNotNull(lastModified, "lastModified of %s", name); + this.headers = headers == null ? ImmutableMultimap. of() : checkNotNull(headers, "headers of %s", name); + this.metadata = metadata == null ? ImmutableMap. of() : metadata; + this.payload = checkNotNull(payload, "payload of %s", name); + } + + /** + * @return The name of this object. + */ + public String getName() { + return name; + } + + /** + * @return The {@link URI} for this object. + */ + public URI getUri() { + return uri; + } + + /** + * @return The ETag of the content of this object. + * @deprecated Please use {@link #getETag()} as this method will be removed in jclouds 1.8. + */ + public String getEtag() { + return etag; + } + + /** + * @return The ETag of the content of this object. + */ + public String getETag() { + return etag; + } + + /** + * @return The {@link Date} that this object was last modified. + */ + public Date getLastModified() { + return lastModified; + } + + /** + * @return The HTTP headers for this object. + */ + public Multimap getHeaders() { + return headers; + } + + /** + *

NOTE

+ * In current swift implementations, headers keys are lower-cased. This means + * characters such as turkish will probably not work out well. + * + * @return a {@code Map} containing this object's metadata. The map is empty + * except in {@link ObjectApi#head(String) GetObjectMetadata} or + * {@link ObjectApi#get(String) GetObject} commands. + */ + public Map getMetadata() { + return metadata; + } + + /** + *

NOTE

+ * The object will only have a {@link Payload#getInput()} when retrieved via the + * {@link ObjectApi#get(String) GetObject} command. + * + * @return The {@link Payload} for this object. + */ + public Payload getPayload() { + return payload; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof SwiftObject) { + final SwiftObject that = SwiftObject.class.cast(object); + return equal(getName(), that.getName()) + && equal(getUri(), that.getUri()) + && equal(getETag(), that.getETag()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getName(), getUri(), getETag()); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return toStringHelper(this) + .add("name", getName()) + .add("uri", getUri()) + .add("etag", getETag()) + .add("lastModified", getLastModified()) + .add("metadata", getMetadata()); + } + + @Override + public int compareTo(SwiftObject that) { + if (that == null) + return 1; + if (this == that) + return 0; + return this.getName().compareTo(that.getName()); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().fromObject(this); + } + + public static class Builder { + protected String name; + protected URI uri; + protected String etag; + protected Date lastModified; + protected Payload payload; + protected Multimap headers = ImmutableMultimap.of(); + protected Map metadata = ImmutableMap.of(); + + /** + * @see SwiftObject#getName() + */ + public Builder name(String name) { + this.name = checkNotNull(name, "name"); + return this; + } + + /** + * @see SwiftObject#getUri() + */ + public Builder uri(URI uri) { + this.uri = checkNotNull(uri, "uri"); + return this; + } + + /** + * @see SwiftObject#getETag() + */ + public Builder etag(String etag) { + this.etag = etag; + return this; + } + + /** + * @see SwiftObject#getLastModified() + */ + public Builder lastModified(Date lastModified) { + this.lastModified = lastModified; + return this; + } + + /** + * @see SwiftObject#getPayload() + */ + public Builder payload(Payload payload) { + this.payload = payload; + return this; + } + + /** + * @see SwiftObject#getHeaders() + */ + public Builder headers(Multimap headers) { + this.headers = headers; + return this; + } + + /** + * Will lower-case all metadata keys due to a swift implementation + * decision. + * + * @see SwiftObject#getMetadata() + */ + public Builder metadata(Map metadata) { + ImmutableMap.Builder builder = ImmutableMap. builder(); + for (Entry entry : checkNotNull(metadata, "metadata").entrySet()) { + builder.put(entry.getKey().toLowerCase(), entry.getValue()); + } + this.metadata = builder.build(); + return this; + } + + public SwiftObject build() { + return new SwiftObject(name, uri, etag, lastModified, headers, metadata, payload); + } + + public Builder fromObject(SwiftObject from) { + return name(from.getName()) + .uri(from.getUri()) + .etag(from.getETag()) + .lastModified(from.getLastModified()) + .headers(from.getHeaders()) + .metadata(from.getMetadata()) + .payload(from.getPayload()); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java new file mode 100644 index 0000000000..c9d5b9de19 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java @@ -0,0 +1,111 @@ +/* + * 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 javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY; + +import java.util.Map; + +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.HEAD; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; + +import org.jclouds.Fallbacks.FalseOnNotFoundOr404; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindAccountMetadataToHeaders; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveAccountMetadataToHeaders; +import org.jclouds.openstack.swift.v1.domain.Account; +import org.jclouds.openstack.swift.v1.functions.ParseAccountFromHeaders; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; + +import com.google.common.annotations.Beta; + +/** + * Provides access to the OpenStack Object Storage (Swift) Account API features. + * + *

+ * Account metadata prefixed with {@code X-Account-Meta-} will be converted + * appropriately using a binder/parser. + *

+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based + * on that feedback, minor changes to the interfaces may happen. This code will replace + * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later. + * + * + * @see {@link Account} + */ +@Beta +@RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) +public interface AccountApi { + + /** + * Gets the {@link Account}. + * + * @return The {@link Account} object. + */ + @Named("account:get") + @HEAD + @ResponseParser(ParseAccountFromHeaders.class) + Account get(); + + /** + * Creates or updates the {@link Account} metadata. + * + * @param metadata the metadata to create or update. + * + * @return {@code true} if the metadata was successfully created or updated, + * {@code false} if not. + */ + @Named("account:updateMetadata") + @POST + @Fallback(FalseOnNotFoundOr404.class) + boolean updateMetadata(@BinderParam(BindAccountMetadataToHeaders.class) Map metadata); + + /** + * Replaces the temporary URL key for the {@link Account}. + * + * @param temporaryUrlKey the temporary URL key to update. + * + * @return {@code true} if the temporary URL key was successfully updated, + * {@code false} if not. + */ + @Named("account:updateTemporaryUrlKey") + @POST + @Fallback(FalseOnNotFoundOr404.class) + boolean updateTemporaryUrlKey(@HeaderParam(ACCOUNT_TEMPORARY_URL_KEY) String temporaryUrlKey); + + /** + * Deletes metadata from the {@link Account}. + * + * @param metadata the metadata to delete. + * + * @return {@code true} if the metadata was successfully deleted, + * {@code false} if not. + */ + @Named("account:deleteMetadata") + @POST + @Fallback(FalseOnNotFoundOr404.class) + boolean deleteMetadata(@BinderParam(BindRemoveAccountMetadataToHeaders.class) Map metadata); + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java new file mode 100644 index 0000000000..b62322fedf --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java @@ -0,0 +1,105 @@ +/* + * 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 com.google.common.collect.Iterables.transform; +import static com.google.common.net.UrlEscapers.urlFragmentEscaper; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN; + +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.v1.binders.SetPayload; +import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse; +import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse; +import org.jclouds.rest.Binder; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.QueryParams; +import org.jclouds.rest.annotations.RequestFilters; + +import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; + +/** + * Provides access to the OpenStack Object Storage (Swift) Bulk API features. + *

+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based + * on that feedback, minor changes to the interfaces may happen. This code will replace + * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later. + */ +@Beta +@RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) +public interface BulkApi { + + /** + * Extracts a tar archive at the path specified as {@code path}. + * + * @param path + * the path to extract under. + * @param payload + * the {@link Payload payload} archive. + * @param format + * one of {@code tar}, {@code tar.gz}, or {@code tar.bz2} + * + * @return {@link BulkDeleteResponse#getErrors()} are empty on success. + */ + @Named("bulk:extractArchive") + @PUT + @Path("/{path}") + ExtractArchiveResponse extractArchive(@PathParam("path") String path, + @BinderParam(SetPayload.class) Payload payload, @QueryParam("extract-archive") String format); + + /** + * Deletes multiple objects or containers, if present. + * + * @param paths + * format of {@code container}, for an empty container, or + * {@code container/object} for an object. + * + * @return {@link BulkDeleteResponse#getErrors()} are empty on success. + */ + @Named("bulk:delete") + @DELETE + @QueryParams(keys = "bulk-delete") + BulkDeleteResponse bulkDelete(@BinderParam(UrlEncodeAndJoinOnNewline.class) Iterable paths); + + // NOTE: this cannot be tested on MWS and is also brittle, as it relies on + // sending a body on DELETE. + // https://bugs.launchpad.net/swift/+bug/1232787 + static class UrlEncodeAndJoinOnNewline implements Binder { + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + String encodedAndNewlineDelimited = Joiner.on('\n').join( + transform(Iterable.class.cast(input), urlFragmentEscaper().asFunction())); + Payload payload = Payloads.newStringPayload(encodedAndNewlineDelimited); + payload.getContentMetadata().setContentType(TEXT_PLAIN); + return (R) request.toBuilder().payload(payload).build(); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java new file mode 100644 index 0000000000..4cbac0d3f5 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java @@ -0,0 +1,215 @@ +/* + * 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 javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.jclouds.openstack.swift.v1.SwiftFallbacks.TrueOn404FalseOn409; + +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.PathParam; + +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.functions.FalseOnAccepted; +import org.jclouds.openstack.swift.v1.functions.ParseContainerFromHeaders; +import org.jclouds.openstack.swift.v1.options.CreateContainerOptions; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.swift.v1.options.UpdateContainerOptions; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; + +import com.google.common.annotations.Beta; +import com.google.common.collect.FluentIterable; + +/** + * Provides access to the OpenStack Object Storage (Swift) Container API features. + *

+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based + * on that feedback, minor changes to the interfaces may happen. This code will replace + * {@code org.jclouds.openstack.swift.SwiftClient} in jclouds 2.0 and it is recommended you adopt it sooner than later. + */ +@Beta +@RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) +public interface ContainerApi { + + /** + * Lists up to 10,000 containers. + * + *

NOTE

+ * This method returns a list of {@link Container} objects without metadata. To retrieve + * the {@link Container} metadata, use the {@link #get(String)} method. + *

+ * + * @return a list of {@link Container containers} ordered by name. + */ + @Named("container:list") + @GET + @Fallback(EmptyFluentIterableOnNotFoundOr404.class) + FluentIterable list(); + + /** + * Lists containers with the supplied {@link ListContainerOptions}. + * + *

NOTE

+ * This method returns a list of {@link Container} objects without metadata. To retrieve + * the {@link Container} metadata, use the {@link #get(String)} method. + *

+ * + * @param options + * the options to control the output list. + * + * @return a list of {@link Container containers} ordered by name. + */ + @Named("container:list") + @GET + @Fallback(EmptyFluentIterableOnNotFoundOr404.class) + FluentIterable list(ListContainerOptions options); + + /** + * Creates a container, if not already present. + * + * @param containerName + * corresponds to {@link Container#getName()}. + * + * @return {@code true} if the container was created, {@code false} if the container already existed. + */ + @Named("container:create") + @PUT + @Path("/{containerName}") + @ResponseParser(FalseOnAccepted.class) + boolean create(@PathParam("containerName") String containerName); + + /** + * Creates a container, if not already present. + * + * @param containerName + * corresponds to {@link Container#getName()}. + * @param options + * the options to use when creating the container. + * + * @return {@code true} if the container was created, {@code false} if the container already existed. + */ + @Named("container:create") + @PUT + @Path("/{containerName}") + @ResponseParser(FalseOnAccepted.class) + boolean create(@PathParam("containerName") String containerName, CreateContainerOptions options); + + /** + * Gets the {@link Container}. + * + * @param containerName + * corresponds to {@link Container#getName()}. + * + * @return the {@link Container}, or {@code null} if not found. + */ + @Named("container:get") + @HEAD + @Path("/{containerName}") + @ResponseParser(ParseContainerFromHeaders.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + Container get(@PathParam("containerName") String containerName); + + /** + * Updates the {@link Container}. + * + * @param containerName + * the container name corresponding to {@link Container#getName()}. + * @param options + * the container options to update. + * + * @return {@code true} if the container metadata was successfully created or updated, + * {@code false} if not. + */ + @Named("container:update") + @POST + @Path("/{containerName}") + @Fallback(FalseOnNotFoundOr404.class) + boolean update(@PathParam("containerName") String containerName, UpdateContainerOptions options); + + /** + * Creates or updates the {@link Container} metadata. + * + * @param containerName + * the container name corresponding to {@link Container#getName()}. + * @param metadata + * the container metadata to create or update. + * + * @return {@code true} if the container metadata was successfully created or updated, + * {@code false} if not. + */ + @Named("container:updateMetadata") + @POST + @Path("/{containerName}") + @Fallback(FalseOnNotFoundOr404.class) + boolean updateMetadata(@PathParam("containerName") String containerName, + @BinderParam(BindContainerMetadataToHeaders.class) Map metadata); + + /** + * Deletes {@link Container} metadata. + * + * @param containerName + * corresponds to {@link Container#getName()}. + * @param metadata + * the container metadata to delete. + * + * @return {@code true} if the container metadata was successfully deleted, + * {@code false} if not. + */ + @Named("container:deleteMetadata") + @POST + @Path("/{containerName}") + @Fallback(FalseOnNotFoundOr404.class) + boolean deleteMetadata(@PathParam("containerName") String containerName, + @BinderParam(BindRemoveContainerMetadataToHeaders.class) Map metadata); + + /** + * Deletes a {@link Container}, if empty. + * + * @param containerName + * corresponds to {@link Container#getName()}. + * + * @return {@code true} if the container was deleted or not present. + * + * @throws IllegalStateException if the container was not empty. + */ + @Named("container:deleteIfEmpty") + @DELETE + @Path("/{containerName}") + @Fallback(TrueOn404FalseOn409.class) + boolean deleteIfEmpty(@PathParam("containerName") String containerName) throws IllegalStateException; + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java new file mode 100644 index 0000000000..4d4d603b9b --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java @@ -0,0 +1,265 @@ +/* + * 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 com.google.common.net.HttpHeaders.EXPECT; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_COPY_FROM; + +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.PathParam; +import javax.ws.rs.Produces; + +import org.jclouds.Fallbacks.FalseOnNotFoundOr404; +import org.jclouds.Fallbacks.NullOnNotFoundOr404; +import org.jclouds.Fallbacks.VoidOnNotFoundOr404; +import org.jclouds.blobstore.BlobStoreFallbacks.FalseOnContainerNotFound; +import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveObjectMetadataToHeaders; +import org.jclouds.openstack.swift.v1.binders.SetPayload; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.functions.ETagHeader; +import org.jclouds.openstack.swift.v1.functions.ParseObjectFromResponse; +import org.jclouds.openstack.swift.v1.functions.ParseObjectListFromResponse; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.swift.v1.options.PutOptions; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.Headers; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; + +import com.google.common.annotations.Beta; + +/** + * Provides access to the OpenStack Object Storage (Swift) Object API features. + *

+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based + * on that feedback, minor changes to the interfaces may happen. This code will replace + * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later. + */ +@Beta +@RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) +public interface ObjectApi { + + /** + * Lists up to 10,000 objects. + * + * @return an {@link ObjectList} of {@link SwiftObject} ordered by name or {@code null}. + */ + @Named("object:list") + @GET + @ResponseParser(ParseObjectListFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + ObjectList list(); + + /** + * Lists up to 10,000 objects. To control a large list of containers beyond + * 10,000 objects, use the {@code marker} and {@code endMarker} parameters in the + * {@link ListContainerOptions} class. + * + * @param options + * the {@link ListContainerOptions} for controlling the returned list. + * + * @return an {@link ObjectList} of {@link SwiftObject} ordered by name or {@code null}. + */ + @Named("object:list") + @GET + @ResponseParser(ParseObjectListFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + ObjectList list(ListContainerOptions options); + + /** + * Creates or updates a {@link SwiftObject}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param payload + * corresponds to {@link SwiftObject#getPayload()}. + * + * @return {@link SwiftObject#getETag()} of the object. + */ + @Named("object:put") + @PUT + @Path("/{objectName}") + @Headers(keys = EXPECT, values = "100-continue") + @ResponseParser(ETagHeader.class) + String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.class) Payload payload); + + /** + * Creates or updates a {@link SwiftObject}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param payload + * corresponds to {@link SwiftObject#getPayload()}. + * @param options + * {@link PutOptions options} to control creating the {@link SwiftObject}. + * + * @return {@link SwiftObject#getETag()} of the object. + */ + @Named("object:put") + @PUT + @Path("/{objectName}") + @Headers(keys = EXPECT, values = "100-continue") + @ResponseParser(ETagHeader.class) + String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.class) Payload payload, + PutOptions options); + + /** + * Gets the {@link SwiftObject} metadata without its {@link Payload#openStream() body}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * + * @return the {@link SwiftObject} or {@code null}, if not found. + */ + @Named("object:getWithoutBody") + @HEAD + @Path("/{objectName}") + @ResponseParser(ParseObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + SwiftObject getWithoutBody(@PathParam("objectName") String objectName); + + /** + * Gets the {@link SwiftObject} including its {@link Payload#openStream() body}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * + * @return the {@link SwiftObject} or {@code null}, if not found. + */ + @Named("object:get") + @GET + @Path("/{objectName}") + @ResponseParser(ParseObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + SwiftObject get(@PathParam("objectName") String objectName); + + /** + * Gets the {@link SwiftObject} including its {@link Payload#openStream() body}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param options + * options to control the download. + * + * @return the {@link SwiftObject} or {@code null}, if not found. + */ + @Named("object:get") + @GET + @Path("/{objectName}") + @ResponseParser(ParseObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + @Nullable + SwiftObject get(@PathParam("objectName") String objectName, GetOptions options); + + /** + * Creates or updates the metadata for a {@link SwiftObject}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param metadata + * the metadata to create or update. + * + * @return {@code true} if the metadata was successfully created or updated, + * {@code false} if not. + */ + @Named("object:updateMetadata") + @POST + @Path("/{objectName}") + @Produces("") + @Fallback(FalseOnNotFoundOr404.class) + boolean updateMetadata(@PathParam("objectName") String objectName, + @BinderParam(BindObjectMetadataToHeaders.class) Map metadata); + + /** + * Deletes the metadata from a {@link SwiftObject}. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param metadata + * corresponds to {@link SwiftObject#getMetadata()}. + * + * @return {@code true} if the metadata was successfully deleted, + * {@code false} if not. + */ + @Named("object:deleteMetadata") + @POST + @Path("/{objectName}") + @Fallback(FalseOnNotFoundOr404.class) + boolean deleteMetadata(@PathParam("objectName") String objectName, + @BinderParam(BindRemoveObjectMetadataToHeaders.class) Map metadata); + + /** + * Deletes an object, if present. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + */ + @Named("object:delete") + @DELETE + @Path("/{objectName}") + @Fallback(VoidOnNotFoundOr404.class) + void delete(@PathParam("objectName") String objectName); + + /** + * Copies an object from one container to another. + * + *

NOTE

+ * This is a server side copy. + * + * @param destinationObject + * the destination object name. + * @param sourceContainer + * the source container name. + * @param sourceObject + * the source object name. + * + * @return {@code true} if the object was successfully copied, {@code false} if not. + * + * @throws org.jclouds.openstack.swift.v1.CopyObjectException if the source or destination container do not exist. + */ + @Named("object:copy") + @PUT + @Path("/{destinationObject}") + @Headers(keys = OBJECT_COPY_FROM, values = "/{sourceContainer}/{sourceObject}") + @Fallback(FalseOnContainerNotFound.class) + boolean copy(@PathParam("destinationObject") String destinationObject, + @PathParam("sourceContainer") String sourceContainer, + @PathParam("sourceObject") String sourceObject); + +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java new file mode 100644 index 0000000000..51ba30d2e9 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java @@ -0,0 +1,90 @@ +/* + * 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 javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import java.util.List; +import java.util.Map; + +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.jclouds.Fallbacks.VoidOnNotFoundOr404; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders; +import org.jclouds.openstack.swift.v1.domain.Segment; +import org.jclouds.openstack.swift.v1.functions.ETagHeader; +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 org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.annotations.Beta; + +/** + * Provides access to the OpenStack Object Storage (Swift) Static Large Object API features. + *

+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based + * on that feedback, minor changes to the interfaces may happen. This code will replace + * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later. + */ +@Beta +@RequestFilters(AuthenticateRequest.class) +@Consumes(APPLICATION_JSON) +@Path("/{objectName}") +public interface StaticLargeObjectApi { + + /** + * Creates or updates a static large object's manifest. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + * @param segments + * ordered parts which will be concatenated upon download. + * @param metadata + * corresponds to {@link SwiftObject#getMetadata()}. + * + * @return {@link SwiftObject#getEtag()} of the object, which is the MD5 + * checksum of the concatenated ETag values of the {@code segments}. + */ + @Named("staticLargeObject:replaceManifest") + @PUT + @ResponseParser(ETagHeader.class) + @QueryParams(keys = "multipart-manifest", values = "put") + String replaceManifest(@PathParam("objectName") String objectName, + @BinderParam(BindToJsonPayload.class) List segments, + @BinderParam(BindObjectMetadataToHeaders.class) Map metadata); + + /** + * Deletes a static large object, if present, including all of its segments. + * + * @param objectName + * corresponds to {@link SwiftObject#getName()}. + */ + @Named("staticLargeObject:delete") + @DELETE + @Fallback(VoidOnNotFoundOr404.class) + @QueryParams(keys = "multipart-manifest", values = "delete") + void delete(@PathParam("objectName") String objectName); +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java new file mode 100644 index 0000000000..25a749b8c5 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java @@ -0,0 +1,32 @@ +/* + * 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 static com.google.common.net.HttpHeaders.ETAG; + +import org.jclouds.http.HttpResponse; + +import com.google.common.base.Function; + +public class ETagHeader implements Function { + + @Override + public String apply(HttpResponse from) { + String etag = from.getFirstHeaderOrNull(ETAG); + return etag != null ? etag.replace("\"", "") : null; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java new file mode 100644 index 0000000000..473da3e43e --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java @@ -0,0 +1,48 @@ +/* + * 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 java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; + +/** + * Extracts entries whose keys start with {@code .*-Meta-}. + * + * @param from + * a {@link Multimap} containing the prefixed headers. + * + * @return the extracted metadata without the prefixed keys. + */ +public enum EntriesWithoutMetaPrefix implements Function, Map> { + INSTANCE; + + @Override + public Map apply(Multimap arg0) { + ImmutableMap.Builder metadata = ImmutableMap.builder(); + for (Entry header : arg0.entries()) { + int index = header.getKey().indexOf("-Meta-"); + if (index != -1) { + metadata.put(header.getKey().substring(index + 6), header.getValue()); + } + } + return metadata.build(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java new file mode 100644 index 0000000000..68da52498c --- /dev/null +++ b/apis/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 { + + @Override + public Boolean apply(HttpResponse from) { + return from.getStatusCode() == 202 ? false : true; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java new file mode 100644 index 0000000000..cdd49f16b8 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java @@ -0,0 +1,31 @@ +/* + * 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 java.util.Map; + +import org.jclouds.http.HttpResponse; + +import com.google.common.base.Function; + +/** Extracts metadata entries from http response headers. */ +public class MetadataFromHeaders implements Function> { + @Override + public Map apply(HttpResponse from) { + return EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders()); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java new file mode 100644 index 0000000000..9debe67f3e --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java @@ -0,0 +1,38 @@ +/* + * 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 static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_BYTES_USED; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_CONTAINER_COUNT; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_OBJECT_COUNT; + +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.swift.v1.domain.Account; + +import com.google.common.base.Function; + +public class ParseAccountFromHeaders implements Function { + + @Override + public Account apply(HttpResponse from) { + return Account.builder() + .bytesUsed(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_BYTES_USED))) + .containerCount(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_CONTAINER_COUNT))) + .objectCount(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_OBJECT_COUNT))) + .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build(); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java new file mode 100644 index 0000000000..d61635117f --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java @@ -0,0 +1,54 @@ +/* + * 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 static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_BYTES_USED; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_OBJECT_COUNT; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; + +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, + InvocationContext { + + String name; + + @Override + public Container apply(HttpResponse from) { + Container c = + Container.builder() + .name(name) + .bytesUsed(Long.parseLong(from.getFirstHeaderOrNull(CONTAINER_BYTES_USED))) + .objectCount(Long.parseLong(from.getFirstHeaderOrNull(CONTAINER_OBJECT_COUNT))) + .anybodyRead(CONTAINER_ACL_ANYBODY_READ.equals(from.getFirstHeaderOrNull(CONTAINER_READ))) + .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build(); + return c; + } + + @Override + public ParseContainerFromHeaders setContext(HttpRequest request) { + this.name = GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString(); + return this; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java new file mode 100644 index 0000000000..cc934e4430 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java @@ -0,0 +1,87 @@ +/* + * 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 static com.google.common.io.BaseEncoding.base16; +import static com.google.common.net.HttpHeaders.ETAG; +import static com.google.common.net.HttpHeaders.LAST_MODIFIED; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT; + +import java.net.URI; +import java.util.Date; + +import javax.inject.Inject; + +import org.jclouds.date.DateService; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.io.MutableContentMetadata; +import org.jclouds.io.Payload; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.rest.InvocationContext; +import org.jclouds.rest.internal.GeneratedHttpRequest; + +import com.google.common.base.Function; +import com.google.common.hash.HashCode; + +public class ParseObjectFromResponse implements Function, + InvocationContext { + private final DateService dates; + + @Inject + ParseObjectFromResponse(DateService dates) { + this.dates = dates; + } + + private String uri; + private String name; + + @Override + public SwiftObject apply(HttpResponse from) { + + Payload payload = from.getPayload(); + MutableContentMetadata contentMeta = payload.getContentMetadata(); + + String deleteAt = from.getFirstHeaderOrNull(OBJECT_DELETE_AT); + if (deleteAt != null) { + long fromEpoch = Long.parseLong(from.getFirstHeaderOrNull(OBJECT_DELETE_AT)) * 1000; + contentMeta.setExpires(new Date(fromEpoch)); + payload.setContentMetadata(contentMeta); + } + + String etag = from.getFirstHeaderOrNull(ETAG); + if (etag != null) { + payload.getContentMetadata().setContentMD5(HashCode.fromBytes(base16().lowerCase().decode(etag))); + } + + return SwiftObject.builder() + .uri(URI.create(uri)) + .name(name) + .etag(etag) + .payload(payload) + .lastModified(dates.rfc822DateParse(from.getFirstHeaderOrNull(LAST_MODIFIED))) + .headers(from.getHeaders()) + .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build(); + } + + @Override + public ParseObjectFromResponse setContext(HttpRequest request) { + this.uri = request.getEndpoint().toString(); + this.name = GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString(); + return this; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java new file mode 100644 index 0000000000..ff12e0fa59 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java @@ -0,0 +1,113 @@ +/* + * 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 static com.google.common.io.BaseEncoding.base16; +import static org.jclouds.http.Uris.uriBuilder; + +import java.util.Date; +import java.util.List; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.rest.InvocationContext; +import org.jclouds.rest.internal.GeneratedHttpRequest; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.hash.HashCode; +import com.google.common.io.ByteSource; + +public class ParseObjectListFromResponse implements Function, + InvocationContext { + + private static final class InternalObject { + String name; + String hash; + long bytes; + String content_type; + Date last_modified; + Date expires; + } + + private final ParseJson> json; + private final ParseContainerFromHeaders parseContainer; + + @Inject + ParseObjectListFromResponse(ParseJson> json, ParseContainerFromHeaders parseContainer) { + this.json = json; + this.parseContainer = parseContainer; + } + + private ToSwiftObject toSwiftObject; + + @Override + public ObjectList apply(HttpResponse from) { + List objects = Lists.transform(json.apply(from), toSwiftObject); + Container container = parseContainer.apply(from); + return ObjectList.create(objects, container); + } + + static class ToSwiftObject implements Function { + private final String containerUri; + + ToSwiftObject(String containerUri) { + this.containerUri = containerUri; + } + + @Override + public SwiftObject apply(InternalObject input) { + return SwiftObject.builder() + .uri(uriBuilder(containerUri).clearQuery().appendPath(input.name).build()) + .name(input.name) + .etag(input.hash) + .payload(payload(input.bytes, input.hash, input.content_type, input.expires)) + .lastModified(input.last_modified).build(); + } + } + + @Override + public ParseObjectListFromResponse setContext(HttpRequest request) { + parseContainer.name = GeneratedHttpRequest.class.cast(request).getCaller().get().getArgs().get(1).toString(); + String containerUri = request.getEndpoint().toString(); + int queryIndex = containerUri.indexOf('?'); + if (queryIndex != -1) { + containerUri = containerUri.substring(0, queryIndex); + } + toSwiftObject = new ToSwiftObject(containerUri); + return this; + } + + private static Payload payload(long bytes, String hash, String contentType, Date expires) { + Payload payload = Payloads.newByteSourcePayload(ByteSource.empty()); + payload.getContentMetadata().setContentLength(bytes); + payload.getContentMetadata().setContentType(contentType); + payload.getContentMetadata().setExpires(expires); + if (hash != null) { + payload.getContentMetadata().setContentMD5(HashCode.fromBytes(base16().lowerCase().decode(hash))); + } + return payload; + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java new file mode 100644 index 0000000000..2bde41c980 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java @@ -0,0 +1,91 @@ +/* + * 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.handlers; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jclouds.blobstore.ContainerNotFoundException; +import org.jclouds.blobstore.KeyNotFoundException; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.openstack.swift.v1.CopyObjectException; +import org.jclouds.openstack.swift.v1.reference.SwiftHeaders; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.InsufficientResourcesException; + +// TODO: is there error spec someplace? let's type errors, etc. +public class SwiftErrorHandler implements HttpErrorHandler { + public static final String PREFIX = "^/v[0-9][^/]*/[a-zA-Z]+_[^/]+/"; + public static final Pattern CONTAINER_PATH = Pattern.compile(PREFIX + "([^/]+)$"); + public static final Pattern CONTAINER_KEY_PATH = Pattern.compile(PREFIX + "([^/]+)/(.*)"); + + public void handleError(HttpCommand command, HttpResponse response) { + // it is important to always read fully and close streams + byte[] data = closeClientButKeepContentStream(response); + String message = data != null ? new String(data) : null; + + Exception exception = message != null ? new HttpResponseException(command, response, message) + : new HttpResponseException(command, response); + message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), + response.getStatusLine()); + switch (response.getStatusCode()) { + case 401: + exception = new AuthorizationException(exception.getMessage(), exception); + break; + case 404: + Exception oldException = exception; + String sourcePath = command.getCurrentRequest().getFirstHeaderOrNull(SwiftHeaders.OBJECT_COPY_FROM); + if (sourcePath != null) { + // the path returned here is in the form "/v1/tenant-id/destContainer/destObject" + String path = command.getCurrentRequest().getEndpoint().getPath(); + int startOfDestinationPath = path.lastIndexOf("/", path.lastIndexOf("/") - 1); + // get the "/destContainer/destObject" portion of the path + String destinationPath = path.substring(startOfDestinationPath); + + exception = new CopyObjectException(sourcePath, destinationPath, message); + exception.initCause(oldException); + } else if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + String path = command.getCurrentRequest().getEndpoint().getPath(); + Matcher matcher = CONTAINER_PATH.matcher(path); + + if (matcher.find()) { + exception = new ContainerNotFoundException(matcher.group(1), message); + exception.initCause(oldException); + } else { + matcher = CONTAINER_KEY_PATH.matcher(path); + if (matcher.find()) { + exception = new KeyNotFoundException(matcher.group(1), matcher.group(2), message); + exception.initCause(oldException); + } + } + } + break; + case 409: + exception = new IllegalStateException(exception.getMessage(), exception); + break; + case 413: + exception = new InsufficientResourcesException(exception.getMessage(), exception); + break; + } + command.setException(exception); + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java new file mode 100644 index 0000000000..2014f616d6 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java @@ -0,0 +1,108 @@ +/* + * 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.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION; + +import java.util.Map; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders; + +import com.google.common.collect.Multimap; + +/** + * Options for creating a {@link Container} + * + * @see ContainerApi#create(String, CreateContainerOptions) + */ +public class CreateContainerOptions extends BaseHttpRequestOptions { + + public static final CreateContainerOptions NONE = new CreateContainerOptions(); + + /** + * Sets the headers on a container at creation. + */ + public CreateContainerOptions headers(Multimap headers) { + this.headers.putAll(headers); + return this; + } + + /** + * Sets the metadata on a container at creation. + */ + public CreateContainerOptions metadata(Map metadata) { + this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata)); + return this; + } + + /** + * Sets the public ACL on the container so that anybody can read it. + */ + public CreateContainerOptions anybodyRead() { + this.headers.put(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ); + return this; + } + + /** + * Sets the container that will contain object versions. + */ + public CreateContainerOptions versionsLocation(String containerName) { + this.headers.put(VERSIONS_LOCATION, containerName); + return this; + } + + public static class Builder { + + /** + * @see CreateContainerOptions#anybodyRead + */ + public static CreateContainerOptions anybodyRead() { + CreateContainerOptions options = new CreateContainerOptions(); + return options.anybodyRead(); + } + + /** + * @see CreateContainerOptions#headers + */ + public static CreateContainerOptions headers(Multimap headers) { + CreateContainerOptions options = new CreateContainerOptions(); + return options.headers(headers); + } + + /** + * @see CreateContainerOptions#metadata + */ + public static CreateContainerOptions metadata(Map metadata) { + CreateContainerOptions options = new CreateContainerOptions(); + return options.metadata(metadata); + } + + /** + * @see CreateContainerOptions#versionsLocation + */ + public static CreateContainerOptions versionsLocation(String containerName) { + CreateContainerOptions options = new CreateContainerOptions(); + return options.versionsLocation(containerName); + } + } + + private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(CONTAINER_METADATA_PREFIX); +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java new file mode 100644 index 0000000000..8b01aae725 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java @@ -0,0 +1,132 @@ +/* + * 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; + +/** + * Options for listing containers. + * + * @see ContainerApi#list(ListContainerOptions) + */ +public class ListContainerOptions extends BaseHttpRequestOptions { + public static final ListContainerOptions NONE = new ListContainerOptions(); + + /** + * list operation returns no more than this amount. + */ + public ListContainerOptions 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; + } + + /** + * object names greater in value than the specified marker are returned. + */ + public ListContainerOptions marker(String marker) { + queryParameters.put("marker", checkNotNull(marker, "marker")); + return this; + } + + /** + * object names less in value than the specified marker are returned. + */ + public ListContainerOptions endMarker(String endMarker) { + queryParameters.put("end_marker", checkNotNull(endMarker, "endMarker")); + return this; + } + + /** + * object names beginning with this substring are returned. + */ + public ListContainerOptions prefix(String prefix) { + queryParameters.put("prefix", checkNotNull(prefix, "prefix")); + return this; + } + + /** + * object names nested in the container are returned. + */ + public ListContainerOptions delimiter(char delimiter) { + queryParameters.put("delimiter", Character.toString(delimiter)); + return this; + } + + /** + * object names nested in the pseudo path are returned. + */ + public ListContainerOptions path(String path) { + queryParameters.put("path", checkNotNull(path, "path")); + return this; + } + + public static class Builder { + + /** + * @see ListContainerOptions#limit + */ + public static ListContainerOptions limit(int limit) { + ListContainerOptions options = new ListContainerOptions(); + return options.limit(limit); + } + + /** + * @see ListContainerOptions#marker + */ + public static ListContainerOptions marker(String marker) { + ListContainerOptions options = new ListContainerOptions(); + return options.marker(marker); + } + + /** + * @see ListContainerOptions#endMarker + */ + public static ListContainerOptions endMarker(String endMarker) { + ListContainerOptions options = new ListContainerOptions(); + return options.endMarker(endMarker); + } + + /** + * @see ListContainerOptions#prefix + */ + public static ListContainerOptions prefix(String prefix) { + ListContainerOptions options = new ListContainerOptions(); + return options.prefix(prefix); + } + + /** + * @see ListContainerOptions#delimiter + */ + public static ListContainerOptions delimiter(char delimiter) { + ListContainerOptions options = new ListContainerOptions(); + return options.delimiter(delimiter); + } + + /** + * @see ListContainerOptions#path + */ + public static ListContainerOptions path(String path) { + ListContainerOptions options = new ListContainerOptions(); + return options.path(path); + } + } +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java new file mode 100644 index 0000000000..d4a1ed3503 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java @@ -0,0 +1,71 @@ +/* + * 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.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; + +import java.util.Map; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders; + +import com.google.common.collect.Multimap; + +/** + * Options for creating an Object. + */ +public class PutOptions extends BaseHttpRequestOptions { + + public static final PutOptions NONE = new PutOptions(); + + /** + * Sets the metadata on a container at creation. + */ + public PutOptions metadata(Map metadata) { + this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata)); + return this; + } + + /** + * Sets the headers on a container at creation. + */ + public PutOptions headers(Multimap headers) { + this.headers.putAll(headers); + return this; + } + + public static class Builder { + + /** + * @see PutOptions#headers + */ + public static PutOptions headers(Multimap headers) { + PutOptions options = new PutOptions(); + return options.headers(headers); + } + + /** + * @see PutOptions#metadata + */ + public static PutOptions metadata(Map metadata) { + PutOptions options = new PutOptions(); + return options.metadata(metadata); + } + } + + private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(OBJECT_METADATA_PREFIX); +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java new file mode 100644 index 0000000000..f74b7fe18a --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java @@ -0,0 +1,107 @@ +/* + * 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.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION; + +import java.util.Map; + +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders; + +import com.google.common.collect.Multimap; + +/** + * Options for updating a {@link Container}. + * + * @see org.jclouds.openstack.swift.v1.features.ContainerApi#update(String, UpdateContainerOptions) + */ +public class UpdateContainerOptions extends BaseHttpRequestOptions { + public static final UpdateContainerOptions NONE = new UpdateContainerOptions(); + + /** + * Sets the headers on a container at creation. + */ + public UpdateContainerOptions headers(Multimap headers) { + this.headers.putAll(headers); + return this; + } + + /** + * Sets the metadata on a container at creation. + */ + public UpdateContainerOptions metadata(Map metadata) { + this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata)); + return this; + } + + /** + * Sets the public ACL on the container so that anybody can read it. + */ + public UpdateContainerOptions anybodyRead() { + this.headers.put(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ); + return this; + } + + /** + * Sets the container that will contain object versions. + */ + public UpdateContainerOptions versionsLocation(String containerName) { + this.headers.put(VERSIONS_LOCATION, containerName); + return this; + } + + public static class Builder { + + /** + * @see UpdateContainerOptions#anybodyRead + */ + public static UpdateContainerOptions anybodyRead() { + UpdateContainerOptions options = new UpdateContainerOptions(); + return options.anybodyRead(); + } + + /** + * @see UpdateContainerOptions#headers + */ + public static UpdateContainerOptions headers(Multimap headers) { + UpdateContainerOptions options = new UpdateContainerOptions(); + return options.headers(headers); + } + + /** + * @see UpdateContainerOptions#metadata + */ + public static UpdateContainerOptions metadata(Map metadata) { + UpdateContainerOptions options = new UpdateContainerOptions(); + return options.metadata(metadata); + } + + /** + * @see UpdateContainerOptions#versionsLocation + */ + public static UpdateContainerOptions versionsLocation(String containerName) { + UpdateContainerOptions options = new UpdateContainerOptions(); + return options.versionsLocation(containerName); + } + } + + private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(CONTAINER_METADATA_PREFIX); +} diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java new file mode 100644 index 0000000000..caab5e7e56 --- /dev/null +++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java @@ -0,0 +1,92 @@ +/* + * 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.reference; + +/** + * Common headers in Swift. + */ +public final class SwiftHeaders { + + // Common Metadata Prefixes + public static final String ACCOUNT_METADATA_PREFIX = "X-Account-Meta-"; + public static final String CONTAINER_METADATA_PREFIX = "X-Container-Meta-"; + public static final String OBJECT_METADATA_PREFIX = "X-Object-Meta-"; + public static final String USER_METADATA_PREFIX = OBJECT_METADATA_PREFIX; + + // Metadata Removal Prefixes + public static final String ACCOUNT_REMOVE_METADATA_PREFIX = "X-Remove-Account-Meta-"; + public static final String CONTAINER_REMOVE_METADATA_PREFIX = "X-Remove-Container-Meta-"; + public static final String OBJECT_REMOVE_METADATA_PREFIX = "X-Remove-Object-Meta-"; + + // TempURL + public static final String ACCOUNT_TEMPORARY_URL_KEY = ACCOUNT_METADATA_PREFIX + "Temp-Url-Key"; + public static final String ACCOUNT_TEMPORARY_URL_KEY_2 = ACCOUNT_TEMPORARY_URL_KEY + "-2"; + + // Account Headers + public static final String ACCOUNT_BYTES_USED = "X-Account-Bytes-Used"; + public static final String ACCOUNT_CONTAINER_COUNT = "X-Account-Container-Count"; + public static final String ACCOUNT_OBJECT_COUNT = "X-Account-Object-Count"; + + // Container Headers + public static final String CONTAINER_BYTES_USED = "X-Container-Bytes-Used"; + public static final String CONTAINER_OBJECT_COUNT = "X-Container-Object-Count"; + + // Public access - not supported in all Swift Impls + public static final String CONTAINER_READ = "X-Container-Read"; + public static final String CONTAINER_WRITE = "X-Container-Write"; + public static final String CONTAINER_ACL_ANYBODY_READ = ".r:*,.rlistings"; + + // CORS + public static final String CONTAINER_ACCESS_CONTROL_ALLOW_ORIGIN = CONTAINER_METADATA_PREFIX + "Access-Control-Allow-Origin"; + public static final String CONTAINER_ACCESS_CONTROL_MAX_AGE = CONTAINER_METADATA_PREFIX + "Access-Control-Max-Age"; + public static final String CONTAINER_ACCESS_CONTROL_EXPOSE_HEADERS = CONTAINER_METADATA_PREFIX + "Access-Control-Expose-Headers"; + + // Container Quota + public static final String CONTAINER_QUOTA_BYTES = CONTAINER_METADATA_PREFIX + "Quota-Bytes"; + public static final String CONTAINER_QUOTA_COUNT = CONTAINER_METADATA_PREFIX + "Quota-Count"; + + // Container Sync + public static final String CONTAINER_SYNC_KEY = "X-Container-Sync-Key"; + public static final String CONTAINER_SYNC_TO = "X-Container-Sync-To"; + + // Versioning + public static final String VERSIONS_LOCATION = "X-Versions-Location"; + + // Misc functionality + public static final String CONTAINER_WEB_MODE = "X-Web-Mode"; + + public static final String OBJECT_COPY_FROM = "X-Copy-From"; + public static final String OBJECT_DELETE_AFTER = "X-Delete-After"; + public static final String OBJECT_DELETE_AT = "X-Delete-At"; + public static final String OBJECT_MANIFEST = "X-Object-Manifest"; + /** Get the newest version of the object for GET and HEAD requests */ + public static final String OBJECT_NEWEST = "X-Newest"; + + // Static Large Object + public static final String STATIC_LARGE_OBJECT = "X-Static-Large-Object"; + + // Static Web + public static final String STATIC_WEB_INDEX = CONTAINER_METADATA_PREFIX + "Web-Index"; + public static final String STATIC_WEB_DIRECTORY_TYPE = CONTAINER_METADATA_PREFIX + "Web-Directory-Type"; + public static final String STATIC_WEB_ERROR = CONTAINER_METADATA_PREFIX + "Web-Error"; + public static final String STATIC_WEB_LISTINGS = CONTAINER_METADATA_PREFIX + "Web-Listings"; + public static final String STATIC_WEB_LISTINGS_CSS = CONTAINER_METADATA_PREFIX + "Web-Listings-CSS"; + + private SwiftHeaders() { + throw new AssertionError("intentionally unimplemented"); + } +} diff --git a/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000000..c5b801728d --- /dev/null +++ b/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1,18 @@ +# +# 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. +# + +org.jclouds.openstack.swift.v1.SwiftApiMetadata diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java new file mode 100644 index 0000000000..8b5e89510a --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java @@ -0,0 +1,71 @@ +/* + * 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; + +import static com.google.common.base.Charsets.UTF_8; +import static org.jclouds.openstack.swift.v1.features.AccountApiMockTest.accountResponse; +import static org.testng.Assert.assertEquals; + +import java.util.Properties; + +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +/** + * @see KeystoneProperties#CREDENTIAL_TYPE + */ +@Test +public class AuthenticationMockTest extends BaseOpenStackMockTest { + + @DataProvider(name = "jclouds.keystone.credential-type") + Object[][] credentialTypeToPostBody() { + Object[][] credentialTypeToPostBody = new Object[2][2]; + credentialTypeToPostBody[0][0] = "apiAccessKeyCredentials"; + credentialTypeToPostBody[0][1] = "{\"auth\":{\"apiAccessKeyCredentials\":{\"accessKey\":\"joe\",\"secretKey\":\"letmein\"},\"tenantName\":\"jclouds\"}}"; + credentialTypeToPostBody[1][0] = "passwordCredentials"; + credentialTypeToPostBody[1][1] = "{\"auth\":{\"passwordCredentials\":{\"username\":\"joe\",\"password\":\"letmein\"},\"tenantName\":\"jclouds\"}}"; + return credentialTypeToPostBody; + } + + @Test(dataProvider = "jclouds.keystone.credential-type") + public void authenticateCredentialType(String credentialType, String expectedPost) throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse())); + + try { + Properties overrides = new Properties(); + overrides.setProperty("jclouds.keystone.credential-type", credentialType); + + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides); + + api.getAccountApi("DFW").get(); + + assertEquals(server.getRequestCount(), 2); + RecordedRequest authRequest = server.takeRequest(); + assertEquals(authRequest.getRequestLine(), "POST /tokens HTTP/1.1"); + assertEquals(new String(authRequest.getBody(), UTF_8), expectedPost); + } finally { + server.shutdown(); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java new file mode 100644 index 0000000000..6fbe4d974c --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java @@ -0,0 +1,32 @@ +/* + * 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; + +import org.jclouds.View; +import org.jclouds.apis.internal.BaseApiMetadataTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +@Test(groups = "unit", testName = "SwiftApiMetadataTest") +// public class SwiftApiMetadataTest extends BaseBlobStoreApiMetadataTest { +public class SwiftApiMetadataTest extends BaseApiMetadataTest { + public SwiftApiMetadataTest() { + super(new SwiftApiMetadata(), ImmutableSet.> of()); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java new file mode 100644 index 0000000000..703da9b455 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java @@ -0,0 +1,117 @@ +/* + * 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; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.verify; +import static org.jclouds.io.Payloads.newByteSourcePayload; + +import java.net.URI; + +import org.easymock.IArgumentMatcher; +import org.jclouds.blobstore.ContainerNotFoundException; +import org.jclouds.blobstore.KeyNotFoundException; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.swift.v1.handlers.SwiftErrorHandler; +import org.testng.annotations.Test; + +import com.google.common.io.ByteSource; + +/** + * Tests the {@link SwiftErrorHandler} + */ +@Test(groups = "unit", testName = "SwiftErrorHandlerTest") +public class SwiftErrorHandlerTest { + + @Test + public void test404SetsKeyNotFoundExceptionMosso() { + assertCodeMakes("HEAD", URI + .create("http://host/v1/MossoCloudFS_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1/key"), 404, + "Not Found", "", KeyNotFoundException.class); + } + + @Test + public void test404SetsKeyNotFoundExceptionSwift() { + assertCodeMakes("HEAD", URI + .create("http://67.202.39.175:8080/v1/AUTH_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1/key"), + 404, "Not Found", "", KeyNotFoundException.class); + } + + @Test + public void test404SetsContainerNotFoundExceptionMosso() { + assertCodeMakes("HEAD", URI + .create("http://host/v1/MossoCloudFS_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1"), 404, + "Not Found", "", ContainerNotFoundException.class); + } + + @Test + public void test404SetsContainerNotFoundExceptionSwift() { + assertCodeMakes("HEAD", URI + .create("http://67.202.39.175:8080/v1/AUTH_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1"), + 404, "Not Found", "", ContainerNotFoundException.class); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content, + Class expected) { + assertCodeMakes(method, uri, statusCode, message, "text/plain", content, expected); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType, + String content, Class expected) { + + SwiftErrorHandler function = new SwiftErrorHandler(); + + HttpCommand command = createMock(HttpCommand.class); + HttpRequest request = HttpRequest.builder().method(method).endpoint(uri).build(); + HttpResponse response = HttpResponse.builder().statusCode(statusCode).message(message) + .payload(newByteSourcePayload(ByteSource.wrap(content.getBytes()))).build(); + response.getPayload().getContentMetadata().setContentType(contentType); + + expect(command.getCurrentRequest()).andReturn(request).atLeastOnce(); + command.setException(classEq(expected)); + + replay(command); + + function.handleError(command, response); + + verify(command); + } + + public static Exception classEq(final Class in) { + reportMatcher(new IArgumentMatcher() { + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("classEq("); + buffer.append(in); + buffer.append(")"); + } + + @Override + public boolean matches(Object arg) { + return arg.getClass() == in; + } + + }); + return null; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java new file mode 100644 index 0000000000..dd65210c9f --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java @@ -0,0 +1,90 @@ +/* + * 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; + +import static java.lang.String.format; +import static org.jclouds.io.Payloads.newByteSourcePayload; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.UUID; + +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest; +import org.jclouds.util.Strings2; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.io.ByteSource; + +@Test(groups = "live", testName = "TemporaryUrlSignerLiveTest") +public class TemporaryUrlSignerLiveTest extends BaseSwiftApiLiveTest { + + private String name = getClass().getSimpleName(); + private String containerName = getClass().getSimpleName() + "Container"; + + public void signForPublicAccess() throws Exception { + for (String regionId : api.getConfiguredRegions()) { + SwiftObject object = api.getObjectApi(regionId, containerName).get(name); + + long expires = System.currentTimeMillis() / 1000 + 5; + String signature = TemporaryUrlSigner.checkApiEvery(api.getAccountApi(regionId), 5) + .sign("GET", object.getUri().getPath(), expires); + + URI signed = URI.create(format("%s?temp_url_sig=%s&temp_url_expires=%s", object.getUri(), signature, expires)); + + InputStream publicStream = signed.toURL().openStream(); + assertEquals(Strings2.toStringAndClose(publicStream), "swifty"); + + // let it expire + Thread.sleep(5000); + try { + signed.toURL().openStream(); + fail("should have expired!"); + } catch (IOException e) { + } + } + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + String key = UUID.randomUUID().toString(); + for (String regionId : api.getConfiguredRegions()) { + api.getAccountApi(regionId).updateTemporaryUrlKey(key); + api.getContainerApi(regionId).create(containerName); + api.getObjectApi(regionId, containerName) + .put(name, newByteSourcePayload(ByteSource.wrap("swifty".getBytes()))); + } + } + + @AfterMethod + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : api.getConfiguredRegions()) { + api.getObjectApi(regionId, containerName).delete(name); + api.getContainerApi(regionId).deleteIfEmpty(containerName); + } + super.tearDown(); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java new file mode 100644 index 0000000000..8f42cf306d --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java @@ -0,0 +1,76 @@ +/* + * 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; + +import static org.jclouds.openstack.swift.v1.features.AccountApiMockTest.accountResponse; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY; +import static org.testng.Assert.assertEquals; + +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; + +@Test(groups = "unit", testName = "TemporaryUrlSignerMockTest") +public class TemporaryUrlSignerMockTest extends BaseOpenStackMockTest { + + @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "accountApi") + public void whenAccountApiIsNull() { + TemporaryUrlSigner.checkApiEvery(null, 10000); + } + + public void whenAccountApiHasKey() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse().addHeader(ACCOUNT_TEMPORARY_URL_KEY, "mykey"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + String signature = TemporaryUrlSigner.checkApiEvery(api.getAccountApi("DFW"), 10000) + .sign("GET", "/v1/AUTH_account/container/object", 1323479485l); + + assertEquals(signature, "d9fc2067e52b06598421664cf6610bfc8fc431f6"); + + 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 HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*returned a null temporaryUrlKey!") + public void whenAccountApiDoesntHaveKey() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse())); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + TemporaryUrlSigner.checkApiEvery(api.getAccountApi("DFW"), 10000) + .sign("GET", "/v1/AUTH_account/container/object", 1323479485l); + } finally { + 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 HTTP/1.1"); + server.shutdown(); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java new file mode 100644 index 0000000000..97f544819a --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java @@ -0,0 +1,159 @@ +/* + * 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.blobstore; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import java.io.IOException; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import org.jclouds.blobstore.BlobRequestSigner; +import org.jclouds.blobstore.BlobStore; +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.domain.PageSet; +import org.jclouds.blobstore.domain.StorageMetadata; +import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest; +import org.jclouds.domain.Location; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.io.ByteStreams2; +import org.jclouds.io.MutableContentMetadata; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.rest.HttpClient; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; +import com.google.common.io.ByteSource; +import com.google.common.hash.Hashing; +import com.google.common.net.MediaType; +import com.google.common.net.HttpHeaders; + +@Test(groups = "live") +public class RegionScopedBlobStoreContextLiveTest extends BaseBlobStoreIntegrationTest { + + public RegionScopedBlobStoreContextLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } + + @Test + public void testRegionsAreNotEmpty() { + assertFalse(RegionScopedBlobStoreContext.class.cast(view).getConfiguredRegions().isEmpty()); + } + + @Test + public void testLocationsMatch() { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.getConfiguredRegions()) { + Set locations = ctx.getBlobStore(regionId).listAssignableLocations(); + assertEquals(locations.size(), 1, "expected one region " + regionId + " " + locations); + Location location = locations.iterator().next(); + assertEquals(location.getId(), regionId, "region id " + regionId + " didn't match getId(): " + location); + } + } + + @Test + public void testListBlobs() throws InterruptedException, ExecutionException { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.getConfiguredRegions()) { + ctx.getBlobStore(regionId).list(); + } + } + + @Test + public void testSign() throws InterruptedException, ExecutionException, + IOException { + RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view); + for (String regionId : ctx.getConfiguredRegions()) { + BlobStore region = ctx.getBlobStore(regionId); + PageSet containers = region.list(); + if (containers.isEmpty()) { + continue; + } + String containerName = Iterables.getLast(containers).getName(); + + final ByteSource input = ByteSource.wrap("str".getBytes()); + final HttpClient client = ctx.utils().http(); + + // test signed put + String blobName = "test-" + UUID.randomUUID(); + Blob blob2 = region.blobBuilder(blobName).forSigning() + .contentLength(input.size()) + .contentMD5(input.hash(Hashing.md5()).asBytes()) + .contentType(MediaType.OCTET_STREAM.toString()).build(); + BlobRequestSigner signer = ctx.getSigner(regionId); + HttpResponse response; + try { + HttpRequest putRequest; + putRequest = signer.signPutBlob(containerName, blob2, 600); + MutableContentMetadata metadata = blob2.getMetadata() + .getContentMetadata(); + HttpRequest.Builder putRequestBuilder = putRequest.toBuilder() + .addHeader(HttpHeaders.CONTENT_TYPE, + metadata.getContentType()); + putRequestBuilder.addHeader(HttpHeaders.CONTENT_LENGTH, + String.valueOf(input.size())); + putRequestBuilder.payload(input); + putRequest = putRequestBuilder.build(); + Payload payload = Payloads.newPayload(input.read()); + putRequest.setPayload(payload); + assertNotNull(putRequest, "regionId=" + regionId + ", container=" + + containerName + ", blob=" + blobName); + response = client.invoke(putRequest); + if (response.getStatusCode() != 200 + && response.getStatusCode() != 201) { + fail("Signed PUT expected to return 200 or 201 but returned " + + response.getStatusCode()); + } + } catch (Exception e) { + fail("Failed signed put test: " + e); + } + + // test signed get + try { + HttpRequest getRequest = signer.signGetBlob(containerName, + blobName); + assertNotNull(getRequest, "regionId=" + regionId + ", container=" + + containerName + ", blob=" + blobName); + response = client.invoke(getRequest); + if (response.getStatusCode() != 200) { + fail("Signed GET expected to return 200 but returned " + + response.getStatusCode()); + } + Payload payload = response.getPayload(); + assertEquals(ByteStreams2.toByteArrayAndClose(payload.openStream()), input.read(), + "Data with signed GET not identical to what was put"); + } catch (Exception e) { + fail("Failed signed GET test: " + e); + } + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java new file mode 100644 index 0000000000..6d26100d16 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java @@ -0,0 +1,76 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; + +import java.util.Properties; + +import org.jclouds.blobstore.domain.Blob; +import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest; +import org.testng.SkipException; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftBlobIntegrationLiveTest") +public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest { + + public SwiftBlobIntegrationLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } + + // Object/Container name contains forbidden chars from "<> + @Override + @DataProvider(name = "delete") + public Object[][] createData() { + return new Object[][] { { "normal" }, { "sp ace" }, { "qu?stion" }, { "unic₪de" }, { "path/foo" }, { "colon:" }, + { "asteri*k" }, { "p|pe" } }; + } + + @Override + public void testGetTwoRanges() { + throw new SkipException("unsupported in swift"); + } + + @Override + public void testCreateBlobWithExpiry() throws InterruptedException { + throw new SkipException("unsupported in swift"); + } + + @Test(groups = { "integration", "live" }) + public void testGetIfUnmodifiedSince() throws InterruptedException { + throw new SkipException("unsupported in swift"); + } + + @Override + protected int getIncorrectContentMD5StatusCode() { + return 422; + } + + @Override + protected void checkContentLanguage(Blob blob, String contentLanguage) { + assert blob.getPayload().getContentMetadata().getContentLanguage() == null; + assert blob.getMetadata().getContentMetadata().getContentLanguage() == null; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java new file mode 100644 index 0000000000..d9996bf43f --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java @@ -0,0 +1,39 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; + +import java.util.Properties; + +import org.jclouds.blobstore.integration.internal.BaseBlobLiveTest; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftBlobLiveTest") +public class SwiftBlobLiveTest extends BaseBlobLiveTest { + + public SwiftBlobLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java new file mode 100644 index 0000000000..9dd603f7c2 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java @@ -0,0 +1,39 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; + +import java.util.Properties; + +import org.jclouds.blobstore.integration.internal.BaseBlobSignerLiveTest; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftBlobSignerLiveTest") +public class SwiftBlobSignerLiveTest extends BaseBlobSignerLiveTest { + + public SwiftBlobSignerLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java new file mode 100644 index 0000000000..d954867c96 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java @@ -0,0 +1,54 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; +import static org.testng.Assert.assertTrue; + +import java.util.Properties; + +import org.jclouds.blobstore.integration.internal.BaseContainerIntegrationTest; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftContainerIntegrationLiveTest") +public class SwiftContainerIntegrationLiveTest extends BaseContainerIntegrationTest { + + public SwiftContainerIntegrationLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } + + @Override + public void testListRootUsesDelimiter() throws InterruptedException { + try { + super.testListRootUsesDelimiter(); + } catch (AssertionError e) { + // swift doesn't have the "common prefixes" in the response that s3 + // does. If we wanted this to pass, we'd need to create + // pseudo-directories implicitly, which is costly and troublesome. It + // is better to fail this assertion. + assertTrue(e.getMessage().matches(".*16.* but .*15.*"), e.getMessage()); + // ^^ squishy regex to deal with various formats of testng messages. + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java new file mode 100644 index 0000000000..9bd85d6f5f --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java @@ -0,0 +1,39 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; + +import java.util.Properties; + +import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftContainerLiveTest") +public class SwiftContainerLiveTest extends BaseContainerLiveTest { + + public SwiftContainerLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java new file mode 100644 index 0000000000..1da1a68d3e --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java @@ -0,0 +1,39 @@ +/* + * 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.blobstore.integration; + +import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE; + +import java.util.Properties; + +import org.jclouds.blobstore.integration.internal.BaseServiceIntegrationTest; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "SwiftServiceIntegrationLiveTest") +public class SwiftServiceIntegrationLiveTest extends BaseServiceIntegrationTest { + + public SwiftServiceIntegrationLiveTest() { + provider = "openstack-swift"; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); + return props; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java new file mode 100644 index 0000000000..e4225fcab5 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java @@ -0,0 +1,90 @@ +/* + * 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.config; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters.BulkDeleteResponseAdapter; +import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters.ExtractArchiveResponseAdapter; +import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse; +import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +@Test +public class SwiftTypeAdaptersTest { + Gson gson = new GsonBuilder() + .registerTypeAdapter(ExtractArchiveResponse.class, new ExtractArchiveResponseAdapter()) + .registerTypeAdapter(BulkDeleteResponse.class, new BulkDeleteResponseAdapter()) + .create(); + + public void extractArchiveWithoutErrors() { + assertEquals(gson.fromJson("" + + "{\n" + + " \"Response Status\": \"201 Created\",\n" + + " \"Response Body\": \"\",\n" + + " \"Errors\": [],\n" + + " \"Number Files Created\": 10\n" + + "}", ExtractArchiveResponse.class), ExtractArchiveResponse.create(10, ImmutableMap. of())); + } + + public void extractArchiveWithErrorsAndDecodesPaths() { + assertEquals( + gson.fromJson("" + + "{\n" + + " \"Response Status\": \"201 Created\",\n" + + " \"Response Body\": \"\",\n" + + " \"Errors\": [\n" + + " [\"/v1/12345678912345/mycontainer/home/xx%3Cyy\", \"400 Bad Request\"],\n" + + " [\"/v1/12345678912345/mycontainer/../image.gif\", \"400 Bad Request\"]\n" + + " ],\n" + + " \"Number Files Created\": 8\n" + + "}", ExtractArchiveResponse.class), + ExtractArchiveResponse.create( + 8, + ImmutableMap. builder() + .put("/v1/12345678912345/mycontainer/home/xx of())); + } + + public void bulkDeleteWithErrorsAndDecodesPaths() { + assertEquals(gson.fromJson("" + + "{\n" + + " \"Response Status\": \"400 Bad Request\",\n" + + " \"Response Body\": \"\",\n" + + " \"Errors\": [\n" + + " [\"/v1/12345678912345/Not%20Empty\", \"409 Conflict\"]" + + " ],\n" + + " \"Number Deleted\": 0\n" + + "}", BulkDeleteResponse.class), + BulkDeleteResponse.create(0, 0, ImmutableMap.of("/v1/12345678912345/Not Empty", "409 Conflict"))); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java new file mode 100644 index 0000000000..3841b33695 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java @@ -0,0 +1,87 @@ +/* + * 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.assertNotNull; +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.Account; +import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; + +@Test(groups = "live", testName = "AccountApiLiveTest") +public class AccountApiLiveTest extends BaseSwiftApiLiveTest { + + public void testGet() throws Exception { + for (String regionId : regions) { + AccountApi accountApi = api.getAccountApi(regionId); + Account account = accountApi.get(); + + assertNotNull(account); + assertTrue(account.getContainerCount() >= 0); + assertTrue(account.getObjectCount() >= 0); + assertTrue(account.getBytesUsed() >= 0); + } + } + + public void testUpdateMetadata() throws Exception { + for (String regionId : regions) { + AccountApi accountApi = api.getAccountApi(regionId); + + Map meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar"); + + assertTrue(accountApi.updateMetadata(meta)); + + accountHasMetadata(accountApi, meta); + } + } + + public void testDeleteMetadata() throws Exception { + for (String regionId : regions) { + AccountApi accountApi = api.getAccountApi(regionId); + + Map meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar"); + + assertTrue(accountApi.updateMetadata(meta)); + accountHasMetadata(accountApi, meta); + + assertTrue(accountApi.deleteMetadata(meta)); + Account account = accountApi.get(); + for (Entry entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertFalse(account.getMetadata().containsKey(entry.getKey().toLowerCase())); + } + } + } + + static void accountHasMetadata(AccountApi accountApi, Map meta) { + Account account = accountApi.get(); + for (Entry entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertEquals(account.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(), + account + " didn't have metadata: " + entry); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java new file mode 100644 index 0000000000..84d08fe099 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java @@ -0,0 +1,145 @@ +/* + * 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.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_BYTES_USED; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_CONTAINER_COUNT; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_OBJECT_COUNT; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_REMOVE_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY; +import static org.testng.Assert.assertEquals; +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.Account; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +@Test(groups = "unit", testName = "AccountApiMockTest") +public class AccountApiMockTest extends BaseOpenStackMockTest { + + /** upper-cases first char, and lower-cases rest!! **/ + public void getKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse() + // note silly casing + .addHeader(ACCOUNT_METADATA_PREFIX + "Apiname", "swift") + .addHeader(ACCOUNT_METADATA_PREFIX + "Apiversion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + Account account = api.getAccountApi("DFW").get(); + assertEquals(account.getContainerCount(), 3l); + assertEquals(account.getObjectCount(), 42l); + assertEquals(account.getBytesUsed(), 323479l); + for (Entry entry : metadata.entrySet()) { + assertEquals(account.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"); + } finally { + server.shutdown(); + } + } + + public void updateMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse() + .addHeader(ACCOUNT_METADATA_PREFIX + "ApiName", "swift") + .addHeader(ACCOUNT_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getAccountApi("DFW").updateMetadata(metadata)); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + + RecordedRequest replaceRequest = server.takeRequest(); + assertRequest(replaceRequest, "POST", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader(ACCOUNT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void updateTemporaryUrlKey() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse())); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getAccountApi("DFW").updateTemporaryUrlKey("foobar")); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + + RecordedRequest replaceRequest = server.takeRequest(); + assertRequest(replaceRequest, "POST", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"); + assertEquals(replaceRequest.getHeader(ACCOUNT_TEMPORARY_URL_KEY), "foobar"); + } finally { + server.shutdown(); + } + } + + public void deleteMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(accountResponse())); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getAccountApi("DFW").deleteMetadata(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 HTTP/1.1"); + for (String key : metadata.keySet()) { + assertEquals(deleteRequest.getHeader(ACCOUNT_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored"); + } + } finally { + server.shutdown(); + } + } + + private static final Map metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1"); + + public static MockResponse accountResponse() { + return new MockResponse() + .addHeader(ACCOUNT_CONTAINER_COUNT, "3") + .addHeader(ACCOUNT_OBJECT_COUNT, "42") + .addHeader(ACCOUNT_BYTES_USED, "323479"); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java new file mode 100644 index 0000000000..6901ac98c5 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java @@ -0,0 +1,125 @@ +/* + * 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.assertTrue; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import org.jboss.shrinkwrap.api.GenericArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.exporter.TarGzExporter; +import org.jclouds.io.ByteStreams2; +import org.jclouds.io.Payload; +import org.jclouds.io.payloads.ByteSourcePayload; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse; +import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse; +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.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.ByteSource; + +@Test(groups = "live", testName = "BulkApiLiveTest") +public class BulkApiLiveTest extends BaseSwiftApiLiveTest { + + private static final int OBJECT_COUNT = 10; + private String containerName = getClass().getSimpleName(); + List paths = Lists.newArrayList(); + byte[] tarGz; + + public void testNotPresentWhenDeleting() throws Exception { + for (String regionId : regions) { + BulkDeleteResponse deleteResponse = api.getBulkApi(regionId).bulkDelete( + ImmutableList.of(UUID.randomUUID().toString())); + assertEquals(deleteResponse.getDeleted(), 0); + assertEquals(deleteResponse.getNotFound(), 1); + assertTrue(deleteResponse.getErrors().isEmpty()); + } + } + + public void testExtractArchive() throws Exception { + for (String regionId : regions) { + Payload payload = new ByteSourcePayload(ByteSource.wrap(tarGz)); + + ExtractArchiveResponse extractResponse = api.getBulkApi(regionId) + .extractArchive(containerName, payload, "tar.gz"); + assertEquals(extractResponse.getCreated(), OBJECT_COUNT); + assertTrue(extractResponse.getErrors().isEmpty()); + assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), OBJECT_COUNT); + + // repeat the command + extractResponse = api.getBulkApi(regionId).extractArchive(containerName, payload, "tar.gz"); + assertEquals(extractResponse.getCreated(), OBJECT_COUNT); + assertTrue(extractResponse.getErrors().isEmpty()); + } + } + + @Test(dependsOnMethods = "testExtractArchive") + public void testBulkDelete() throws Exception { + for (String regionId : regions) { + BulkDeleteResponse deleteResponse = api.getBulkApi(regionId).bulkDelete(paths); + assertEquals(deleteResponse.getDeleted(), OBJECT_COUNT); + assertEquals(deleteResponse.getNotFound(), 0); + assertTrue(deleteResponse.getErrors().isEmpty()); + assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 0); + } + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + for (String regionId : regions) { + boolean created = api.getContainerApi(regionId).create(containerName); + if (!created) { + deleteAllObjectsInContainer(regionId, containerName); + } + } + GenericArchive files = ShrinkWrap.create(GenericArchive.class, "files.tar.gz"); + StringAsset content = new StringAsset("foo"); + for (int i = 0; i < OBJECT_COUNT; i++) { + paths.add(containerName + "/file" + i); + files.add(content, "/file" + i); + } + + try { + tarGz = ByteStreams2.toByteArrayAndClose(files.as(TarGzExporter.class).exportAsInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : regions) { + deleteAllObjectsInContainer(regionId, containerName); + api.getContainerApi(regionId).deleteIfEmpty(containerName); + } + super.tearDown(); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java new file mode 100644 index 0000000000..3136b52161 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java @@ -0,0 +1,72 @@ +/* + * 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.assertTrue; + +import org.jboss.shrinkwrap.api.GenericArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.exporter.TarGzExporter; +import org.jclouds.io.ByteStreams2; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import com.google.common.io.ByteSource; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +@Test(groups = "unit", testName = "BulkApiMockTest") +public class BulkApiMockTest extends BaseOpenStackMockTest { + + public void testExtractArchive() throws Exception { + GenericArchive files = ShrinkWrap.create(GenericArchive.class, "files.tar.gz"); + StringAsset content = new StringAsset("foo"); + for (int i = 0; i < 10; i++) { + files.add(content, "/file" + i); + } + + byte[] tarGz = ByteStreams2.toByteArrayAndClose(files.as(TarGzExporter.class).exportAsInputStream()); + + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody("{\"Number Files Created\": 10, \"Errors\": []}"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + Payload payload = Payloads.newByteSourcePayload(ByteSource.wrap(tarGz)); + ExtractArchiveResponse response = api.getBulkApi("DFW").extractArchive("myContainer", payload, "tar.gz"); + + assertEquals(response.getCreated(), 10); + assertTrue(response.getErrors().isEmpty()); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + RecordedRequest extractRequest = server.takeRequest(); + assertRequest(extractRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer?extract-archive=tar.gz"); + assertEquals(extractRequest.getBody(), tarGz); + } finally { + server.shutdown(); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java new file mode 100644 index 0000000000..bf2ba98fa4 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java @@ -0,0 +1,197 @@ +/* + * 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.assertNotNull; +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.BaseSwiftApiLiveTest; +import org.jclouds.openstack.swift.v1.options.CreateContainerOptions; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.swift.v1.options.UpdateContainerOptions; +import org.jclouds.openstack.swift.v1.reference.SwiftHeaders; +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; +import com.google.common.collect.ImmutableMultimap; + +/** + * Provides live tests for the {@link ContainerApi}. + */ +@Test(groups = "live", testName = "ContainerApiLiveTest") +public class ContainerApiLiveTest extends BaseSwiftApiLiveTest { + + private String name = getClass().getSimpleName(); + + public void testCreateWithOptions() throws Exception { + for (String regionId : regions) { + ImmutableMultimap headers = + ImmutableMultimap.of(SwiftHeaders.STATIC_WEB_INDEX, "__index.html", + SwiftHeaders.STATIC_WEB_ERROR, "__error.html"); + CreateContainerOptions opts = new CreateContainerOptions().headers(headers); + + assertNotNull(api.getContainerApi(regionId).create(name, opts)); + + Container container = api.getContainerApi(regionId).get(name); + assertNotNull(container); + assertEquals(container.getName(), name); + assertEquals(container.getMetadata().size(), 2); + assertEquals(container.getMetadata().get("web-index"), "__index.html"); + assertEquals(container.getMetadata().get("web-error"), "__error.html"); + + assertTrue(api.getContainerApi(regionId).deleteIfEmpty(name)); + } + } + + public void testCreateWithSpacesAndSpecialCharacters() throws Exception { + final String nameWithSpaces = "container # ! special"; + + for (String regionId : regions) { + assertTrue(api.getContainerApi(regionId).create(nameWithSpaces)); + Container container = api.getContainerApi(regionId).get(nameWithSpaces); + assertNotNull(container); + assertEquals(container.getName(), nameWithSpaces); + + assertTrue(api.getContainerApi(regionId).deleteIfEmpty(nameWithSpaces)); + } + } + + public void testList() throws Exception { + for (String regionId : regions) { + ContainerApi containerApi = api.getContainerApi(regionId); + FluentIterable response = containerApi.list(); + assertNotNull(response); + for (Container container : response) { + assertNotNull(container.getName()); + assertTrue(container.getObjectCount() >= 0); + assertTrue(container.getBytesUsed() >= 0); + } + } + } + + public void testListWithOptions() throws Exception { + String lexicographicallyBeforeName = name.substring(0, name.length() - 1); + for (String regionId : regions) { + ListContainerOptions options = ListContainerOptions.Builder.marker(lexicographicallyBeforeName); + Container container = api.getContainerApi(regionId).list(options).get(0); + assertEquals(container.getName(), name); + assertTrue(container.getObjectCount() == 0); + assertTrue(container.getBytesUsed() == 0); + } + } + + public void testUpdate() throws Exception { + for (String regionId : regions) { + ImmutableMultimap headers = + ImmutableMultimap.of(SwiftHeaders.STATIC_WEB_INDEX, "__index.html", + SwiftHeaders.STATIC_WEB_ERROR, "__error.html"); + UpdateContainerOptions opts = new UpdateContainerOptions().headers(headers); + + assertNotNull(api.getContainerApi(regionId).create(name)); + + Container container = api.getContainerApi(regionId).get(name); + assertNotNull(container); + assertEquals(container.getName(), name); + assertTrue(container.getMetadata().isEmpty()); + + assertNotNull(api.getContainerApi(regionId).update(name, opts)); + + Container updatedContainer = api.getContainerApi(regionId).get(name); + assertNotNull(updatedContainer); + assertEquals(updatedContainer.getName(), name); + assertEquals(updatedContainer.getMetadata().size(), 2); + assertEquals(updatedContainer.getMetadata().get("web-index"), "__index.html"); + assertEquals(updatedContainer.getMetadata().get("web-error"), "__error.html"); + + assertTrue(api.getContainerApi(regionId).deleteIfEmpty(name)); + } + } + + public void testGet() throws Exception { + for (String regionId : regions) { + Container container = api.getContainerApi(regionId).get(name); + assertEquals(container.getName(), name); + assertTrue(container.getObjectCount() == 0); + assertTrue(container.getBytesUsed() == 0); + } + } + + public void testUpdateMetadata() throws Exception { + Map meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar"); + + for (String regionId : regions) { + ContainerApi containerApi = api.getContainerApi(regionId); + assertTrue(containerApi.updateMetadata(name, meta)); + containerHasMetadata(containerApi, name, meta); + } + } + + public void testDeleteMetadata() throws Exception { + Map meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar"); + + for (String regionId : regions) { + ContainerApi containerApi = api.getContainerApi(regionId); + // update + assertTrue(containerApi.updateMetadata(name, meta)); + containerHasMetadata(containerApi, name, meta); + // delete + assertTrue(containerApi.deleteMetadata(name, meta)); + Container container = containerApi.get(name); + for (Entry entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertFalse(container.getMetadata().containsKey(entry.getKey().toLowerCase())); + } + } + } + + static void containerHasMetadata(ContainerApi containerApi, String name, Map meta) { + Container container = containerApi.get(name); + for (Entry entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(), + container + " didn't have metadata: " + entry); + } + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + for (String regionId : regions) { + api.getContainerApi(regionId).create(name); + } + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : regions) { + api.getContainerApi(regionId).deleteIfEmpty(name); + } + super.tearDown(); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java new file mode 100644 index 0000000000..6b3ef66c2b --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java @@ -0,0 +1,363 @@ +/* + * 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.jclouds.openstack.swift.v1.options.CreateContainerOptions.Builder.anybodyRead; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_BYTES_USED; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_OBJECT_COUNT; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_REMOVE_METADATA_PREFIX; +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.blobstore.ContainerNotFoundException; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.Container; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +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; + +@Test(groups = "unit", testName = "ContainerApiMockTest") +public class ContainerApiMockTest extends BaseOpenStackMockTest { + + public void testList() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/container_list.json")))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + ImmutableList containers = api.getContainerApi("DFW").list().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); + assertAuthentication(server); + assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"); + } finally { + server.shutdown(); + } + } + + public void testListWithOptions() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/container_list.json")))); + + ListContainerOptions options = ListContainerOptions.Builder.marker("test"); + assertNotNull(options); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + ImmutableList containers = api.getContainerApi("DFW").list(options).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); + assertAuthentication(server); + assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9?marker=test"); + } finally { + server.shutdown(); + } + } + + public void testContainerExists() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201))); + server.enqueue(addCommonHeaders(containerResponse() + .addHeader(CONTAINER_METADATA_PREFIX + "ApiName", "swift") + .addHeader(CONTAINER_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata))); + + Container container = api.getContainerApi("DFW").get("myContainer"); + assertEquals(container.getName(), "myContainer"); + assertEquals(container.getObjectCount(), 42l); + assertEquals(container.getBytesUsed(), 323479l); + for (Entry entry : container.getMetadata().entrySet()) { + assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + assertEquals(server.getRequestCount(), 3); + assertAuthentication(server); + assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + @Test(expectedExceptions = ContainerNotFoundException.class) + public void testContainerDoesNotExist() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata))); + + // the head call will throw the ContainerNotFoundException + api.getContainerApi("DFW").get("myContainer"); + } finally { + server.shutdown(); + } + } + + public void testCreate() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("DFW").create("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + public void testCreateWithOptions() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata))); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + + RecordedRequest createRequest = server.takeRequest(); + assertRequest(createRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + + assertEquals(createRequest.getHeader(CONTAINER_READ), CONTAINER_ACL_ANYBODY_READ); + + for (Entry entry : metadata.entrySet()) { + assertEquals(createRequest.getHeader(CONTAINER_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void testCreateWithSpacesAndSpecialCharacters() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("DFW").create("container # ! special")); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/container%20%23%20%21%20special"); + } finally { + server.shutdown(); + } + } + + public void testAlreadyCreated() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(202))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertFalse(api.getContainerApi("DFW").create("myContainer")); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + /** upper-cases first char, and lower-cases rest!! **/ + public void testGetKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse() + // note silly casing + .addHeader(CONTAINER_METADATA_PREFIX + "Apiname", "swift") + .addHeader(CONTAINER_METADATA_PREFIX + "Apiversion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + Container container = api.getContainerApi("DFW").get("myContainer"); + assertEquals(container.getName(), "myContainer"); + assertEquals(container.getObjectCount(), 42l); + assertEquals(container.getBytesUsed(), 323479l); + for (Entry entry : container.getMetadata().entrySet()) { + assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + public void testUpdateMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse() + .addHeader(CONTAINER_METADATA_PREFIX + "ApiName", "swift") + .addHeader(CONTAINER_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("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 entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader(CONTAINER_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void testDeleteMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse())); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("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(CONTAINER_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored"); + } + } finally { + server.shutdown(); + } + } + + public void testDeleteIfEmpty() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("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 testAlreadyDeleted() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getContainerApi("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 testDeleteWhenNotEmpty() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(409))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertFalse(api.getContainerApi("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 static final Map metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1"); + + static MockResponse containerResponse() { + return new MockResponse() + .addHeader(CONTAINER_OBJECT_COUNT, "42") + .addHeader(CONTAINER_BYTES_USED, "323479"); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java new file mode 100644 index 0000000000..09e34b543d --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java @@ -0,0 +1,47 @@ +/* + * 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.jclouds.openstack.swift.v1.options.CreateContainerOptions.Builder.anybodyRead; +import static org.testng.Assert.assertTrue; + +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +@Test(groups = "live", testName = "CreatePublicContainerLiveTest") +public class CreatePublicContainerLiveTest extends BaseSwiftApiLiveTest { + + private String name = getClass().getSimpleName(); + + public void testAnybodyReadUpdatesMetadata() throws Exception { + for (String regionId : api.getConfiguredRegions()) { + api.getContainerApi(regionId).create(name, anybodyRead()); + assertTrue(api.getContainerApi(regionId).get(name).getAnybodyRead().get()); + } + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : api.getConfiguredRegions()) { + api.getContainerApi(regionId).deleteIfEmpty(name); + } + super.tearDown(); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java new file mode 100644 index 0000000000..5a472ff654 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java @@ -0,0 +1,286 @@ +/* + * 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.jclouds.http.options.GetOptions.Builder.tail; +import static org.jclouds.io.Payloads.newByteSourcePayload; +import static org.jclouds.openstack.swift.v1.options.ListContainerOptions.Builder.marker; +import static org.jclouds.util.Strings2.toStringAndClose; +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 static org.testng.Assert.fail; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.openstack.swift.v1.CopyObjectException; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteSource; + +/** + * Provides live tests for the {@link ObjectApi}. + */ +@Test(groups = "live", testName = "ObjectApiLiveTest", singleThreaded = true) +public class ObjectApiLiveTest extends BaseSwiftApiLiveTest { + + private String name = getClass().getSimpleName(); + private String containerName = getClass().getSimpleName() + "Container"; + static final Payload PAYLOAD = newByteSourcePayload(ByteSource.wrap("swifty".getBytes())); + + public void testCreateWithSpacesAndSpecialCharacters() throws Exception { + final String containerName = "container # ! special"; + final String objectName = "object # ! special"; + + for (String regionId : regions) { + assertNotNull(api.getContainerApi(regionId).create(containerName)); + assertNotNull(api.getObjectApi(regionId, containerName).put(objectName, PAYLOAD)); + + SwiftObject object = api.getObjectApi(regionId, containerName).get(objectName); + assertEquals(object.getName(), objectName); + checkObject(object); + assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty"); + + api.getObjectApi(regionId, containerName).delete(objectName); + api.getContainerApi(regionId).deleteIfEmpty(containerName); + } + } + + public void testPutWithExpiration() throws Exception { + String objectName = "test-expiration"; + + long expireMillis = new Date().getTime() + 1000 * 60 * 60 * 24; + Date expireAt = new Date(expireMillis); + + Payload payload = newByteSourcePayload(ByteSource.wrap("swifty".getBytes())); + payload.getContentMetadata().setExpires(expireAt); + + for (String regionId : regions) { + String etag = api.getObjectApi(regionId, containerName).put(objectName, payload); + assertNotNull(etag); + + SwiftObject object = api.getObjectApi(regionId, containerName).get(objectName); + assertEquals(object.getName(), objectName); + checkObject(object); + assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty"); + + api.getObjectApi(regionId, containerName).delete(objectName); + } + } + public void testCopyObject() throws Exception { + for (String regionId : regions) { + // source + String sourceContainer = "src" + containerName; + String sourceObjectName = "original.txt"; + String badSource = "badSource"; + + // destination + String destinationContainer = "dest" + containerName; + String destinationObject = "copy.txt"; + String destinationPath = "/" + destinationContainer + "/" + destinationObject; + + ContainerApi containerApi = api.getContainerApi(regionId); + + // create source and destination dirs + containerApi.create(sourceContainer); + containerApi.create(destinationContainer); + + // get the api for this region and container + ObjectApi srcApi = api.getObjectApi(regionId, sourceContainer); + ObjectApi destApi = api.getObjectApi(regionId, destinationContainer); + + // Create source object + assertNotNull(srcApi.put(sourceObjectName, PAYLOAD)); + SwiftObject sourceObject = srcApi.get(sourceObjectName); + checkObject(sourceObject); + + // Create the destination object + assertNotNull(destApi.put(destinationObject, PAYLOAD)); + SwiftObject object = destApi.get(destinationObject); + checkObject(object); + + // check the copy operation + assertTrue(destApi.copy(destinationObject, sourceContainer, sourceObjectName)); + assertNotNull(destApi.get(destinationObject)); + + // now get a real SwiftObject + SwiftObject destSwiftObject = destApi.get(destinationObject); + assertEquals(toStringAndClose(destSwiftObject.getPayload().openStream()), "swifty"); + + // test exception thrown on bad source name + try { + destApi.copy(destinationObject, badSource, sourceObjectName); + fail("Expected CopyObjectException"); + } catch (CopyObjectException e) { + assertEquals(e.getSourcePath(), "/" + badSource + "/" + sourceObjectName); + assertEquals(e.getDestinationPath(), destinationPath); + } + + deleteAllObjectsInContainer(regionId, sourceContainer); + containerApi.deleteIfEmpty(sourceContainer); + + deleteAllObjectsInContainer(regionId, destinationContainer); + containerApi.deleteIfEmpty(destinationContainer); + } + } + + public void testList() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApi(regionId, containerName); + ObjectList response = objectApi.list(); + assertEquals(response.getContainer(), api.getContainerApi(regionId).get(containerName)); + for (SwiftObject object : response) { + checkObject(object); + } + } + } + + public void testListWithOptions() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApi(regionId, containerName); + ObjectList response = objectApi.list(ListContainerOptions.NONE); + assertEquals(response.getContainer(), api.getContainerApi(regionId).get(containerName)); + for (SwiftObject object : response) { + checkObject(object); + } + } + } + + public void testMetadata() throws Exception { + for (String regionId : regions) { + SwiftObject object = api.getObjectApi(regionId, containerName).get(name); + assertEquals(object.getName(), name); + checkObject(object); + assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty"); + } + } + + public void testUpdateMetadata() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApi(regionId, containerName); + + Map meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar"); + assertTrue(objectApi.updateMetadata(name, meta)); + + SwiftObject object = objectApi.get(name); + for (Entry entry : meta.entrySet()) { + // note keys are returned in lower-case! + assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(), + object + " didn't have metadata: " + entry); + } + } + } + + public void testGet() throws Exception { + for (String regionId : regions) { + SwiftObject object = api.getObjectApi(regionId, containerName).get(name, GetOptions.NONE); + assertEquals(object.getName(), name); + checkObject(object); + assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty"); + } + } + + public void testPrivateByDefault() throws Exception { + for (String regionId : regions) { + SwiftObject object = api.getObjectApi(regionId, containerName).get(name); + try { + object.getUri().toURL().openStream(); + fail("shouldn't be able to access " + object); + } catch (IOException expected) { + } + } + } + + public void testGetOptions() throws Exception { + for (String regionId : regions) { + SwiftObject object = api.getObjectApi(regionId, containerName).get(name, tail(1)); + assertEquals(object.getName(), name); + checkObject(object); + assertEquals(toStringAndClose(object.getPayload().openStream()), "y"); + } + } + + public void testListOptions() throws Exception { + String lexicographicallyBeforeName = name.substring(0, name.length() - 1); + for (String regionId : regions) { + SwiftObject object = api.getObjectApi(regionId, containerName) + .list(marker(lexicographicallyBeforeName)).get(0); + assertEquals(object.getName(), name); + checkObject(object); + } + } + + public void testDeleteMetadata() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApi(regionId, containerName); + + Map meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar"); + + assertTrue(objectApi.updateMetadata(name, meta)); + assertFalse(objectApi.get(name).getMetadata().isEmpty()); + + assertTrue(objectApi.deleteMetadata(name, meta)); + assertTrue(objectApi.get(name).getMetadata().isEmpty()); + } + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + for (String regionId : regions) { + api.getContainerApi(regionId).create(containerName); + api.getObjectApi(regionId, containerName).put(name, PAYLOAD); + } + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : regions) { + deleteAllObjectsInContainer(regionId, containerName); + api.getObjectApi(regionId, containerName).delete(name); + api.getContainerApi(regionId).deleteIfEmpty(containerName); + } + + super.tearDown(); + } + + static void checkObject(SwiftObject object) { + assertNotNull(object.getName()); + assertNotNull(object.getUri()); + assertNotNull(object.getETag()); + assertTrue(object.getLastModified().getTime() <= System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5)); + assertNotNull(object.getPayload().getContentMetadata().getContentLength()); + assertNotNull(object.getPayload().getContentMetadata().getContentType()); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java new file mode 100644 index 0000000000..8c0e4d1406 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java @@ -0,0 +1,515 @@ +/* + * 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 com.google.common.base.Charsets.US_ASCII; +import static com.google.common.io.BaseEncoding.base16; +import static com.google.common.net.HttpHeaders.EXPIRES; +import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; +import static org.jclouds.Constants.PROPERTY_RETRY_DELAY_START; +import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT; +import static org.jclouds.http.options.GetOptions.Builder.tail; +import static org.jclouds.io.Payloads.newByteSourcePayload; +import static org.jclouds.openstack.swift.v1.features.ContainerApiMockTest.containerResponse; +import static org.jclouds.openstack.swift.v1.options.ListContainerOptions.Builder.marker; +import static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_REMOVE_METADATA_PREFIX; +import static org.jclouds.util.Strings2.toStringAndClose; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.IOException; +import java.net.URI; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpResponseException; +import org.jclouds.io.Payload; +import org.jclouds.io.payloads.ByteSourcePayload; +import org.jclouds.openstack.swift.v1.CopyObjectException; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.swift.v1.reference.SwiftHeaders; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteSource; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +/** + * Provides mock tests for the {@link ObjectApi}. + */ +@Test(groups = "unit", testName = "ObjectApiMockTest") +public class ObjectApiMockTest extends BaseOpenStackMockTest { + SimpleDateFormatDateService dates = new SimpleDateFormatDateService(); + + static final Payload PAYLOAD = newByteSourcePayload(ByteSource.wrap("swifty".getBytes())); + + protected ImmutableList parsedObjectsForUrl(String baseUri) { + baseUri += "v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"; + return ImmutableList.of( + SwiftObject.builder() + .name("test_obj_1") + .uri(URI.create(baseUri + "/test_obj_1")) + .etag("4281c348eaf83e70ddce0e07221c3d28") + .payload(payload(14, "application/octet-stream", new Date(1406243553))) + .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(), + SwiftObject.builder() + .name("test_obj_2") + .uri(URI.create(baseUri + "/test_obj_2")) + .etag("b039efe731ad111bc1b0ef221c3849d0") + .payload(payload(64l, "application/octet-stream", null)) + .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(), + SwiftObject.builder() + .name("test obj 3") + .uri(URI.create(baseUri + "/test%20obj%203")) + .etag("0b2e80bd0744d9ebb20484149a57c82e") + .payload(payload(14, "application/octet-stream", new Date())) + .lastModified(dates.iso8601DateParse("2014-05-20T05:26:32.612278")).build()); + } + + public void testList() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse() + .addHeader(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ) + .setBody(stringFromResource("/object_list.json")))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + ObjectList objects = api.getObjectApi("DFW", "myContainer").list(); + assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString())); + assertEquals(objects.getContainer().getName(), "myContainer"); + assertTrue(objects.getContainer().getAnybodyRead().get()); + + // Check MD5 is parsed from the ETag header. + SwiftObject object1 = objects.get(1); + assertEquals(base16().lowerCase().decode(object1.getETag()), + object1.getPayload().getContentMetadata().getContentMD5AsHashCode().asBytes()); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + public void testListWithOptions() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse() + .addHeader(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ) + .setBody(stringFromResource("/object_list.json")))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + ObjectList objects = api.getObjectApi("DFW", "myContainer").list(new ListContainerOptions()); + assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString())); + assertEquals(objects.getContainer().getName(), "myContainer"); + assertTrue(objects.getContainer().getAnybodyRead().get()); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer"); + } finally { + server.shutdown(); + } + } + + public void testListOptions() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(containerResponse().setBody(stringFromResource("/object_list.json")))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + ObjectList objects = api.getObjectApi("DFW", "myContainer").list(marker("test")); + assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString())); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer?marker=test"); + } finally { + server.shutdown(); + } + } + + public void testCreate() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse() + .setResponseCode(201) + .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")) + .addHeader("Expires", "1406243553")); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertEquals( + api.getObjectApi("DFW", "myContainer").put("myObject", PAYLOAD, + metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b"); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + RecordedRequest replace = server.takeRequest(); + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject"); + + assertEquals(new String(replace.getBody()), "swifty"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replace.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void testCreateWithSpacesAndSpecialCharacters() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))); + + final String containerName = "container # ! special"; + final String objectName = "object # ! special"; + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertEquals( + api.getObjectApi("DFW", containerName).put(objectName, PAYLOAD, + metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b"); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + RecordedRequest replace = server.takeRequest(); + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/container%20%23%20%21%20special/object%20%23%20%21%20special"); + + assertEquals(new String(replace.getBody()), "swifty"); + } finally { + server.shutdown(); + } + } + + public void testCreateWith408Retry() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 1 + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 2 + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 3 + + // Finally success + server.enqueue(addCommonHeaders(new MockResponse() + .setResponseCode(201) + .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))); + + try { + Properties overrides = new Properties(); + overrides.setProperty(PROPERTY_MAX_RETRIES, 5 + ""); + + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides); + assertEquals( + api.getObjectApi("DFW", "myContainer").put("myObject", PAYLOAD, + metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b"); + + assertEquals(server.getRequestCount(), 5); + assertAuthentication(server); + RecordedRequest replace = server.takeRequest(); + // This should take a while. + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject"); + + assertEquals(new String(replace.getBody()), "swifty"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replace.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + /** upper-cases first char, and lower-cases rest!! **/ + public void testGetWithoutKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse() + // note silly casing + .addHeader(OBJECT_METADATA_PREFIX + "Apiname", "swift") + .addHeader(OBJECT_METADATA_PREFIX + "Apiversion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + SwiftObject object = api.getObjectApi("DFW", "myContainer").getWithoutBody("myObject"); + assertEquals(object.getName(), "myObject"); + assertEquals(object.getETag(), "8a964ee2a5e88be344f36c22562a6486"); + + // Check MD5 is parsed from the ETag header. + assertEquals(base16().lowerCase().decode(object.getETag()), + object.getPayload().getContentMetadata().getContentMD5AsHashCode().asBytes()); + + assertEquals(object.getLastModified(), dates.rfc822DateParse("Fri, 12 Jun 2010 13:40:18 GMT")); + for (Entry entry : object.getMetadata().entrySet()) { + assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + assertEquals(object.getPayload().getContentMetadata().getContentType(), "text/plain; charset=UTF-8"); + assertEquals(toStringAndClose(object.getPayload().openStream()), ""); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject"); + } finally { + server.shutdown(); + } + } + + public void testGet() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse() + // note silly casing + .addHeader(OBJECT_METADATA_PREFIX + "Apiname", "swift") + .addHeader(OBJECT_METADATA_PREFIX + "Apiversion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + SwiftObject object = api.getObjectApi("DFW", "myContainer").get("myObject", tail(1)); + assertEquals(object.getName(), "myObject"); + assertEquals(object.getETag(), "8a964ee2a5e88be344f36c22562a6486"); + assertEquals(object.getLastModified(), dates.rfc822DateParse("Fri, 12 Jun 2010 13:40:18 GMT")); + for (Entry entry : object.getMetadata().entrySet()) { + assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue()); + } + + Payload payload = object.getPayload(); + assertEquals(payload.getContentMetadata().getContentLength(), Long.valueOf(4)); + assertEquals(payload.getContentMetadata().getContentType(), "text/plain; charset=UTF-8"); + assertEquals(payload.getContentMetadata().getExpires(), dates.rfc822DateParse("Wed, 23 Jul 2014 14:00:00 GMT")); + + assertEquals(toStringAndClose(payload.openStream()), "ABCD"); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest get = server.takeRequest(); + assertEquals(get.getRequestLine(), + "GET /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test(expectedExceptions = HttpResponseException.class, timeOut = 20000) + public void testCreateWithTimeout() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + // Typically we would enqueue a response for the put. However, in this case, test the timeout by not providing one. + + try { + Properties overrides = new Properties(); + + overrides.setProperty(PROPERTY_SO_TIMEOUT, 5000 + ""); // This time-outs the connection + overrides.setProperty(PROPERTY_MAX_RETRIES, 0 + ""); // 0 retries == 1 try. Semantics. + overrides.setProperty(PROPERTY_RETRY_DELAY_START, 0 + ""); // exponential backoff already working for this call. This is the delay BETWEEN attempts. + + final SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides); + + api.getObjectApi("DFW", "myContainer").put("myObject", new ByteSourcePayload(ByteSource.wrap("swifty".getBytes())), metadata(metadata)); + + fail("testReplaceTimeout test should have failed with an HttpResponseException."); + } finally { + server.shutdown(); + } + } + + public void testUpdateMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse() + .addHeader(OBJECT_METADATA_PREFIX + "ApiName", "swift") + .addHeader(OBJECT_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getObjectApi("DFW", "myContainer").updateMetadata("myObject", 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/myObject HTTP/1.1"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void testUpdateMetadataContentType() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse() + .addHeader(OBJECT_METADATA_PREFIX + "ApiName", "swift") + .addHeader(OBJECT_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getObjectApi("DFW", "myContainer").updateMetadata("myObject", metadata)); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest replaceRequest = server.takeRequest(); + assertEquals(replaceRequest.getHeaders("Content-Type").get(0), "", "updateMetadata should send an empty content-type header, but sent " + + replaceRequest.getHeaders("Content-Type").get(0).toString()); + + assertEquals(replaceRequest.getRequestLine(), + "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + + public void testDeleteMetadata() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse())); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getObjectApi("DFW", "myContainer").deleteMetadata("myObject", 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/myObject HTTP/1.1"); + for (String key : metadata.keySet()) { + assertEquals(deleteRequest.getHeader(OBJECT_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored"); + } + } finally { + server.shutdown(); + } + } + + public void testDelete() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + api.getObjectApi("DFW", "myContainer").delete("myObject"); + + 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/myObject HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void testAlreadyDeleted() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + api.getObjectApi("DFW", "myContainer").delete("myObject"); + + 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/myObject HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void testCopyObject() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201) + .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bar/foo.txt"))); + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertTrue(api.getObjectApi("DFW", "foo") + .copy("bar.txt", "bar", "foo.txt")); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + + RecordedRequest copyRequest = server.takeRequest(); + assertEquals(copyRequest.getRequestLine(), + "PUT /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/foo/bar.txt HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test(expectedExceptions = CopyObjectException.class) + public void testCopyObjectFail() throws InterruptedException, IOException { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404) + .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bogus/foo.txt"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + // the following line will throw the CopyObjectException + api.getObjectApi("DFW", "foo").copy("bar.txt", "bogus", "foo.txt"); + } finally { + server.shutdown(); + } + } + + private static final Map metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1"); + + static MockResponse objectResponse() { + return new MockResponse() + .addHeader("Last-Modified", "Fri, 12 Jun 2010 13:40:18 GMT") + .addHeader("ETag", "8a964ee2a5e88be344f36c22562a6486") + // TODO: MWS doesn't allow you to return content length w/o content + // on HEAD! + .setBody("ABCD".getBytes(US_ASCII)) + .addHeader("Content-Length", "4") + .addHeader("Content-Type", "text/plain; charset=UTF-8") + .addHeader(EXPIRES, "Wed, 23 Jul 2014 14:00:00 GMT"); + } + + static Payload payload(long bytes, String contentType, Date expires) { + Payload payload = newByteSourcePayload(ByteSource.empty()); + payload.getContentMetadata().setContentLength(bytes); + payload.getContentMetadata().setContentType(contentType); + payload.getContentMetadata().setExpires(expires); + return payload; + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java new file mode 100644 index 0000000000..3e92af5c55 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java @@ -0,0 +1,129 @@ +/* + * 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 java.lang.String.format; +import static org.jclouds.io.Payloads.newByteSourcePayload; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.Segment; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +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.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteSource; + +@Test(groups = "live", testName = "StaticLargeObjectApiLiveTest") +public class StaticLargeObjectApiLiveTest extends BaseSwiftApiLiveTest { + + private String name = getClass().getSimpleName(); + private String containerName = getClass().getSimpleName() + "Container"; + private byte[] megOf1s; + private byte[] megOf2s; + + public void testNotPresentWhenDeleting() throws Exception { + for (String regionId : regions) { + api.getStaticLargeObjectApi(regionId, containerName).delete(UUID.randomUUID().toString()); + } + } + + public void testReplaceManifest() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApi(regionId, containerName); + + String etag1s = objectApi.put(name + "/1", newByteSourcePayload(ByteSource.wrap(megOf1s))); + assertMegabyteAndETagMatches(regionId, name + "/1", etag1s); + + String etag2s = objectApi.put(name + "/2", newByteSourcePayload(ByteSource.wrap(megOf2s))); + assertMegabyteAndETagMatches(regionId, name + "/2", etag2s); + + List segments = ImmutableList. builder() + .add(Segment.builder() + .path(format("%s/%s/1", containerName, name)).etag(etag1s).sizeBytes(1024 * 1024) + .build()) + .add(Segment.builder() + .path(format("%s/%s/2", containerName, name)).etag(etag2s).sizeBytes(1024 * 1024) + .build()) + .build(); + + String etagOfEtags = api.getStaticLargeObjectApi(regionId, containerName).replaceManifest( + name, segments, ImmutableMap.of("myfoo", "Bar")); + + assertNotNull(etagOfEtags); + + SwiftObject bigObject = api.getObjectApi(regionId, containerName).get(name); + assertEquals(bigObject.getETag(), etagOfEtags); + assertEquals(bigObject.getPayload().getContentMetadata().getContentLength(), Long.valueOf(2 * 1024 * 1024)); + assertEquals(bigObject.getMetadata(), ImmutableMap.of("myfoo", "Bar")); + + // segments are visible + assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 3); + } + } + + @Test(dependsOnMethods = "testReplaceManifest") + public void testDelete() throws Exception { + for (String regionId : regions) { + api.getStaticLargeObjectApi(regionId, containerName).delete(name); + assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 0); + } + } + + protected void assertMegabyteAndETagMatches(String regionId, String name, String etag1s) { + SwiftObject object1s = api.getObjectApi(regionId, containerName).get(name); + assertEquals(object1s.getETag(), etag1s); + assertEquals(object1s.getPayload().getContentMetadata().getContentLength(), Long.valueOf(1024 * 1024)); + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + for (String regionId : regions) { + boolean created = api.getContainerApi(regionId).create(containerName); + if (!created) { + deleteAllObjectsInContainer(regionId, containerName); + } + } + + megOf1s = new byte[1024 * 1024]; + megOf2s = new byte[1024 * 1024]; + + Arrays.fill(megOf1s, (byte) 1); + Arrays.fill(megOf2s, (byte) 2); + } + + @Override + @AfterClass(groups = "live") + public void tearDown() { + for (String regionId : regions) { + deleteAllObjectsInContainer(regionId, containerName); + api.getContainerApi(regionId).deleteIfEmpty(containerName); + } + super.tearDown(); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java new file mode 100644 index 0000000000..c5627a6ce7 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java @@ -0,0 +1,107 @@ +/* + * 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.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; +import static org.testng.Assert.assertEquals; + +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.Segment; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HttpHeaders; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +@Test(groups = "unit", testName = "StaticLargeObjectApiMockTest") +public class StaticLargeObjectApiMockTest extends BaseOpenStackMockTest { + + public void testReplaceManifest() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().addHeader(HttpHeaders.ETAG, "\"abcd\""))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + assertEquals( + api.getStaticLargeObjectApi("DFW", "myContainer").replaceManifest( + "myObject", + ImmutableList + . builder() + .add(Segment.builder().path("/mycontainer/objseg1").etag("0228c7926b8b642dfb29554cd1f00963") + .sizeBytes(1468006).build()) + .add(Segment.builder().path("/mycontainer/pseudodir/seg-obj2") + .etag("5bfc9ea51a00b790717eeb934fb77b9b").sizeBytes(1572864).build()) + .add(Segment.builder().path("/other-container/seg-final") + .etag("b9c3da507d2557c1ddc51f27c54bae51").sizeBytes(256).build()).build(), + ImmutableMap.of("MyFoo", "Bar")), "abcd"); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + + RecordedRequest replaceRequest = server.takeRequest(); + assertRequest(replaceRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=put"); + assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + "myfoo"), "Bar"); + assertEquals( + new String(replaceRequest.getBody()), + "[{\"path\":\"/mycontainer/objseg1\",\"etag\":\"0228c7926b8b642dfb29554cd1f00963\",\"size_bytes\":1468006}," + + "{\"path\":\"/mycontainer/pseudodir/seg-obj2\",\"etag\":\"5bfc9ea51a00b790717eeb934fb77b9b\",\"size_bytes\":1572864}," + + "{\"path\":\"/other-container/seg-final\",\"etag\":\"b9c3da507d2557c1ddc51f27c54bae51\",\"size_bytes\":256}]"); + } finally { + server.shutdown(); + } + } + + public void testDelete() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + api.getStaticLargeObjectApi("DFW", "myContainer").delete("myObject"); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "DELETE", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=delete"); + + } finally { + server.shutdown(); + } + } + + public void testAlreadyDeleted() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + api.getStaticLargeObjectApi("DFW", "myContainer").delete("myObject"); + + assertEquals(server.getRequestCount(), 2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "DELETE", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=delete"); + } finally { + server.shutdown(); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java new file mode 100644 index 0000000000..c811571c59 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java @@ -0,0 +1,44 @@ +/* + * 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 org.jclouds.http.HttpRequest; +import org.jclouds.openstack.swift.v1.features.BulkApi.UrlEncodeAndJoinOnNewline; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +@Test(groups = "unit", testName = "UrlEncodeAndJoinOnNewlineTest") +public class UrlEncodeAndJoinOnNewlineTest { + UrlEncodeAndJoinOnNewline binder = new UrlEncodeAndJoinOnNewline(); + + public void urlEncodesPaths() { + HttpRequest request = HttpRequest.builder() + .method("DELETE") + .endpoint("https://storage101.dfw1.clouddrive.com/v1/MossoCloudFS_XXXXXX/") + .addQueryParam("bulk-delete").build(); + + request = binder.bindToRequest(request, ImmutableList. builder() + .add("/v1/12345678912345/mycontainer/home/xx extends BaseApiLiveTest { + + protected Set regions; + + protected BaseSwiftApiLiveTest() { + provider = "openstack-swift"; + } + + @Override + @BeforeClass(groups = "live") + public void setup() { + super.setup(); + String providedRegion = System.getProperty("test." + LocationConstants.PROPERTY_REGION); + if (providedRegion != null) { + regions = ImmutableSet.of(providedRegion); + } else { + regions = api.getConfiguredRegions(); + } + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE); + setIfTestSystemPropertyPresent(props, LocationConstants.PROPERTY_REGION); + return props; + } + + protected void deleteAllObjectsInContainer(String regionId, final String containerName) { + Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS); + + ObjectList objects = api.getObjectApi(regionId, containerName).list(new ListContainerOptions()); + if (objects == null) { + return; + } + List pathsToDelete = Lists.transform(objects, new Function() { + public String apply(SwiftObject input) { + return containerName + "/" + input.getName(); + } + }); + if (!pathsToDelete.isEmpty()) { + BulkDeleteResponse response = api.getBulkApi(regionId).bulkDelete(pathsToDelete); + checkState(response.getErrors().isEmpty(), "Errors deleting paths %s: %s", pathsToDelete, response); + } + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java new file mode 100644 index 0000000000..1da648bfe4 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java @@ -0,0 +1,69 @@ +/* + * 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.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_QUOTA_BYTES; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +/** + * Tests behavior of {@link CreateContainerOptions}. + */ +@Test(groups = "unit") +public class CreateContainerOptionsTest { + + public void testMetadata() { + CreateContainerOptions options = + new CreateContainerOptions().metadata(ImmutableMap.of("ApiName", "swift", "metaKey2", "Value2", "METAKEY3", "VALUE 3 ")); + + Multimap headers = options.buildRequestHeaders(); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey2"), ImmutableList.of("Value2")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey3"), ImmutableList.of("VALUE 3 ")); + } + + public void testHeaders() { + CreateContainerOptions options = + new CreateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_QUOTA_BYTES, "5120", CONTAINER_METADATA_PREFIX + "apiname", "swift")); + + Multimap headers = options.buildRequestHeaders(); + assertEquals(headers.get(CONTAINER_QUOTA_BYTES), ImmutableList.of("5120")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift")); + } + + public void testAnybodyRead() { + CreateContainerOptions options = + new CreateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ)); + assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ)); + } + + public void testVersionsLocation() { + CreateContainerOptions options = + new CreateContainerOptions().headers(ImmutableMultimap.of(VERSIONS_LOCATION, "containerWithVersions")); + assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions")); + } +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java new file mode 100644 index 0000000000..fddf4cd6f7 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java @@ -0,0 +1,48 @@ +/* + * 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.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; + +/** + * Tests behavior of {@link PutOptions}. + */ +@Test(groups = "unit") +public class PutOptionsTest { + + public void testPutMetadata() { + PutOptions options = + new PutOptions().metadata(ImmutableMap.of("ApiName", "swift")); + assertEquals(options.buildRequestHeaders().get(OBJECT_METADATA_PREFIX + "apiname"), ImmutableList.of("swift")); + + } + + public void testPutHeaders() { + PutOptions options = + new PutOptions().headers(ImmutableMultimap.of(OBJECT_DELETE_AT, "123456789")); + assertEquals(options.buildRequestHeaders().get(OBJECT_DELETE_AT), ImmutableList.of("123456789")); + } + +} diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java new file mode 100644 index 0000000000..00f95f76d8 --- /dev/null +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java @@ -0,0 +1,122 @@ +/* + * 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.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_QUOTA_BYTES; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_DIRECTORY_TYPE; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_ERROR; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_INDEX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_LISTINGS; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_LISTINGS_CSS; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.net.MediaType; + +/** + * Tests behavior of {@link UpdateContainerOptions}. + */ +@Test(groups = "unit") +public class UpdateContainerOptionsTest { + + public void testAnybodyRead() { + UpdateContainerOptions options = new UpdateContainerOptions().anybodyRead(); + assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ)); + } + + public void testAnybodyReadViaHeaders() { + UpdateContainerOptions options = + new UpdateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ)); + assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ)); + } + + public void testVersionsLocation() { + UpdateContainerOptions options = new UpdateContainerOptions().versionsLocation("containerWithVersions"); + assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions")); + } + + public void testVersionsLocationViaHeaders() { + UpdateContainerOptions options = + new UpdateContainerOptions().headers(ImmutableMultimap.of(VERSIONS_LOCATION, "containerWithVersions")); + assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions")); + } + + public void testMetadata() { + UpdateContainerOptions options = + new UpdateContainerOptions().metadata(ImmutableMap.of("ApiName", "swift", "metaKey2", "Value2", "METAKEY3", "VALUE 3 ")); + + Multimap headers = options.buildRequestHeaders(); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey2"), ImmutableList.of("Value2")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey3"), ImmutableList.of("VALUE 3 ")); + } + + public void testHeaders() { + UpdateContainerOptions options = + new UpdateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_QUOTA_BYTES, "5120", CONTAINER_METADATA_PREFIX + "apiname", "swift")); + + Multimap headers = options.buildRequestHeaders(); + assertEquals(headers.get(CONTAINER_QUOTA_BYTES), ImmutableList.of("5120")); + assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift")); + } + + public void testStaticWebsiteDirectoryType() { + MediaType appDir = MediaType.create("application", "directory"); + Multimap headers = ImmutableMultimap.of(STATIC_WEB_DIRECTORY_TYPE, appDir.toString()); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_DIRECTORY_TYPE), ImmutableList.of(appDir.toString())); + } + + public void testStaticWebsiteIndexPage() { + Multimap headers = ImmutableMultimap.of(STATIC_WEB_INDEX, "index.html"); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_INDEX), ImmutableList.of("index.html")); + } + + public void testStaticWebsiteErrorPage() { + Multimap headers = ImmutableMultimap.of(STATIC_WEB_ERROR, "error.html"); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_ERROR), ImmutableList.of("error.html")); + } + + public void testEnableStaticWebsiteListings() { + Multimap headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS, "true"); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS), ImmutableList.of("true")); + } + + public void testDiableStaticWebsiteListings() { + Multimap headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS, "false"); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS), ImmutableList.of("false")); + } + + public void testStaticWebsiteListingsCSS() { + Multimap headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS_CSS, "listings.css"); + UpdateContainerOptions options = new UpdateContainerOptions().headers(headers); + assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS_CSS), ImmutableList.of("listings.css")); + } +} diff --git a/apis/openstack-swift/src/test/resources/access.json b/apis/openstack-swift/src/test/resources/access.json new file mode 100644 index 0000000000..8e0a69c78b --- /dev/null +++ b/apis/openstack-swift/src/test/resources/access.json @@ -0,0 +1,249 @@ +{ + "access":{ + "token":{ + "id":"bb03a23aa8271291a7aaa9aaa2aaaaaa", + "expires":"2013-08-02T16:55:24.229-05:00", + "tenant":{ + "id":"888888", + "name":"888888" + }, + "RAX-AUTH:authenticatedBy":[ + "PASSWORD" + ] + }, + "serviceCatalog":[ + { + "name":"cloudFilesCDN", + "endpoints":[ + { + "region":"ORD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"DFW", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"SYD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + } + ], + "type":"rax:object-cdn" + }, + { + "name":"cloudFiles", + "endpoints":[ + { + "region":"ORD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"DFW", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"SYD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + } + ], + "type":"object-store" + }, + { + "name":"cloudLoadBalancers", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:load-balancer" + }, + { + "name":"cloudDatabases", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:database" + }, + { + "name":"cloudBlockStorage", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + } + ], + "type":"volume" + }, + { + "name":"cloudServersOpenStack", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/syd.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/syd.servers.api.rackspacecloud.com\/", + "versionId":"2" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/dfw.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/dfw.servers.api.rackspacecloud.com\/", + "versionId":"2" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/ord.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/ord.servers.api.rackspacecloud.com\/", + "versionId":"2" + } + ], + "type":"compute" + }, + { + "name":"autoscale", + "endpoints":[ + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":null, + "versionList":null, + "versionId":"1.0" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":null, + "versionList":null, + "versionId":"1.0" + } + ], + "type":"rax:autoscale" + }, + { + "name":"cloudMonitoring", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:monitor" + }, + { + "name":"cloudBackup", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:backup" + }, + { + "name":"cloudServers", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":"https:\/\/servers.api.rackspacecloud.com\/v1.0", + "versionList":"https:\/\/servers.api.rackspacecloud.com\/", + "versionId":"1.0" + } + ], + "type":"compute" + }, + { + "name":"cloudDNS", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:dns" + } + ], + "user":{ + "id":"335853", + "roles":[ + { + "id":"10000150", + "description":"Checkmate Access role", + "name":"checkmate" + }, + { + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "id":"5", + "description":"A Role that allows a user access to keystone Service methods", + "name":"object-store:default" + }, + { + "tenantId":"888888", + "id":"6", + "description":"A Role that allows a user access to keystone Service methods", + "name":"compute:default" + }, + { + "id":"3", + "description":"User Admin Role.", + "name":"identity:user-admin" + } + ], + "name":"test", + "RAX-AUTH:defaultRegion":"ORD" + } + } +} diff --git a/apis/openstack-swift/src/test/resources/container_list.json b/apis/openstack-swift/src/test/resources/container_list.json new file mode 100644 index 0000000000..554f5de299 --- /dev/null +++ b/apis/openstack-swift/src/test/resources/container_list.json @@ -0,0 +1,12 @@ +[ + { + "name": "test_container_1", + "count": 2, + "bytes": 78 + }, + { + "name": "test_container_2", + "count": 1, + "bytes": 17 + } +] diff --git a/apis/openstack-swift/src/test/resources/logback.xml b/apis/openstack-swift/src/test/resources/logback.xml new file mode 100644 index 0000000000..ce891f1098 --- /dev/null +++ b/apis/openstack-swift/src/test/resources/logback.xml @@ -0,0 +1,71 @@ + + + + + target/test-data/jclouds.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-wire.log + + + %d %-5p [%c] [%thread] %m%n + + + + + target/test-data/jclouds-blobstore.log + + + %d %-5p [%c] [%thread] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apis/openstack-swift/src/test/resources/object_list.json b/apis/openstack-swift/src/test/resources/object_list.json new file mode 100644 index 0000000000..afe8c77470 --- /dev/null +++ b/apis/openstack-swift/src/test/resources/object_list.json @@ -0,0 +1,23 @@ +[ + { + "name": "test_obj_1", + "hash": "4281c348eaf83e70ddce0e07221c3d28", + "bytes": 14, + "content_type": "application/octet-stream", + "last_modified": "2009-02-03T05:26:32.612278" + }, + { + "name": "test_obj_2", + "hash": "b039efe731ad111bc1b0ef221c3849d0", + "bytes": 64, + "content_type": "application/octet-stream", + "last_modified": "2009-02-03T05:26:32.612278" + }, + { + "name": "test obj 3", + "hash": "0b2e80bd0744d9ebb20484149a57c82e", + "bytes": 123, + "content_type": "application/octet-stream", + "last_modified": "2014-05-20T05:26:32.612278" + } +] diff --git a/apis/pom.xml b/apis/pom.xml index 832a27da42..3415dafa6e 100644 --- a/apis/pom.xml +++ b/apis/pom.xml @@ -45,6 +45,7 @@ openstack-cinder openstack-nova openstack-nova-ec2 + openstack-swift openstack-trove cloudfiles cloudservers