From 32927018834fe4dc138b7e3fbeed57e196bcfb36 Mon Sep 17 00:00:00 2001 From: Everett Toews Date: Wed, 31 Oct 2012 16:07:55 -0500 Subject: [PATCH] Added support for the OpenStack Folsom Cinder Block Storage API. --- labs/openstack-cinder/pom.xml | 122 ++++++ .../openstack/cinder/v1/CinderApi.java | 80 ++++ .../cinder/v1/CinderApiMetadata.java | 108 ++++++ .../openstack/cinder/v1/CinderAsyncApi.java | 77 ++++ .../cinder/v1/config/CinderParserModule.java | 34 ++ .../v1/config/CinderRestClientModule.java | 94 +++++ .../openstack/cinder/v1/domain/Snapshot.java | 244 ++++++++++++ .../openstack/cinder/v1/domain/Volume.java | 352 ++++++++++++++++++ .../cinder/v1/domain/VolumeAttachment.java | 178 +++++++++ .../cinder/v1/domain/VolumeType.java | 194 ++++++++++ .../cinder/v1/features/SnapshotApi.java | 58 +++ .../cinder/v1/features/SnapshotAsyncApi.java | 105 ++++++ .../cinder/v1/features/VolumeApi.java | 61 +++ .../cinder/v1/features/VolumeAsyncApi.java | 105 ++++++ .../cinder/v1/features/VolumeTypeApi.java | 33 ++ .../v1/features/VolumeTypeAsyncApi.java | 68 ++++ .../v1/handlers/CinderErrorHandler.java | 73 ++++ .../v1/options/CreateSnapshotOptions.java | 140 +++++++ .../v1/options/CreateVolumeOptions.java | 240 ++++++++++++ .../services/org.jclouds.apis.ApiMetadata | 1 + .../v1/features/SnapshotApiExpectTest.java | 303 +++++++++++++++ .../VolumeAndSnapshotApiLiveTest.java | 210 +++++++++++ .../v1/features/VolumeApiExpectTest.java | 234 ++++++++++++ .../v1/features/VolumeTypeApiExpectTest.java | 92 +++++ .../v1/features/VolumeTypeApiLiveTest.java | 66 ++++ .../v1/internal/BaseCinderApiExpectTest.java | 29 ++ .../v1/internal/BaseCinderApiLiveTest.java | 65 ++++ .../v1/internal/BaseCinderExpectTest.java | 74 ++++ .../src/test/resources/snapshot_create.json | 1 + .../resources/snapshot_create_response.json | 11 + .../src/test/resources/snapshot_get.json | 13 + .../test/resources/snapshot_list_details.json | 15 + .../test/resources/snapshot_list_simple.json | 13 + .../src/test/resources/volume_create.json | 1 + .../resources/volume_create_response.json | 15 + .../src/test/resources/volume_get.json | 22 ++ .../test/resources/volume_list_details.json | 24 ++ .../test/resources/volume_list_simple.json | 24 ++ .../src/test/resources/volume_type_get.json | 14 + .../resources/volume_type_list_simple.json | 16 + labs/pom.xml | 4 +- 41 files changed, 3612 insertions(+), 1 deletion(-) create mode 100644 labs/openstack-cinder/pom.xml create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApiMetadata.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderAsyncApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderParserModule.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderRestClientModule.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Snapshot.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeAttachment.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeType.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotAsyncApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeAsyncApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeAsyncApi.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/handlers/CinderErrorHandler.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateSnapshotOptions.java create mode 100644 labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateVolumeOptions.java create mode 100644 labs/openstack-cinder/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/SnapshotApiExpectTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeApiExpectTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiExpectTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiLiveTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiExpectTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiLiveTest.java create mode 100644 labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderExpectTest.java create mode 100644 labs/openstack-cinder/src/test/resources/snapshot_create.json create mode 100644 labs/openstack-cinder/src/test/resources/snapshot_create_response.json create mode 100644 labs/openstack-cinder/src/test/resources/snapshot_get.json create mode 100644 labs/openstack-cinder/src/test/resources/snapshot_list_details.json create mode 100644 labs/openstack-cinder/src/test/resources/snapshot_list_simple.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_create.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_create_response.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_get.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_list_details.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_list_simple.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_type_get.json create mode 100644 labs/openstack-cinder/src/test/resources/volume_type_list_simple.json diff --git a/labs/openstack-cinder/pom.xml b/labs/openstack-cinder/pom.xml new file mode 100644 index 0000000000..a37cf406dd --- /dev/null +++ b/labs/openstack-cinder/pom.xml @@ -0,0 +1,122 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.6.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + openstack-cinder + jclouds openstack-cinder api + jclouds components to access an implementation of OpenStack Cinder + bundle + + + + http://localhost:5000/v2.0/ + + 1 + + FIXME_IDENTITY + FIXME_CREDENTIALS + passwordCredentials + + org.jclouds.openstack.cinder.v1*;version="${project.version}" + + org.jclouds.rest.internal;version="${project.version}", + org.jclouds*;version="${project.version}", + * + + + + + + org.jclouds + jclouds-core + ${project.version} + + + org.jclouds.api + openstack-keystone + ${project.version} + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds.api + openstack-keystone + ${project.version} + test-jar + test + + + ch.qos.logback + logback-classic + 1.0.0 + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.openstack-cinder.endpoint} + ${test.openstack-cinder.api-version} + ${test.openstack-cinder.build-version} + ${test.openstack-cinder.identity} + ${test.openstack-cinder.credential} + ${test.jclouds.keystone.credential-type} + ${jclouds.blobstore.httpstream.url} + ${jclouds.blobstore.httpstream.md5} + + + + + + + + + + + diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApi.java new file mode 100644 index 0000000000..bc2bd5069f --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApi.java @@ -0,0 +1,80 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.location.Zone; +import org.jclouds.location.functions.ZoneToEndpoint; +import org.jclouds.openstack.cinder.v1.features.SnapshotApi; +import org.jclouds.openstack.cinder.v1.features.VolumeApi; +import org.jclouds.openstack.cinder.v1.features.VolumeTypeApi; +import org.jclouds.openstack.v2_0.features.ExtensionApi; +import org.jclouds.rest.annotations.Delegate; +import org.jclouds.rest.annotations.EndpointParam; + +import com.google.inject.Provides; + +/** + * Provides synchronous access to Cinder. + * + * @see CinderAsyncApi + * @see API Doc + * @author Everett Toews + */ +@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS) +public interface CinderApi { + /** + * @return the Zone codes configured + */ + @Provides + @Zone + Set getConfiguredZones(); + + /** + * Provides synchronous access to Extension features. + */ + @Delegate + ExtensionApi getExtensionApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Volume features. + */ + @Delegate + VolumeApi getVolumeApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to VolumeType features. + */ + @Delegate + VolumeTypeApi getVolumeTypeApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Snapshot features. + */ + @Delegate + SnapshotApi getSnapshotApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApiMetadata.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApiMetadata.java new file mode 100644 index 0000000000..3a0827a27c --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderApiMetadata.java @@ -0,0 +1,108 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.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 java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.openstack.cinder.v1.config.CinderParserModule; +import org.jclouds.openstack.cinder.v1.config.CinderRestClientModule; +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.ZoneModule; +import org.jclouds.openstack.v2_0.ServiceType; +import org.jclouds.rest.RestContext; +import org.jclouds.rest.internal.BaseRestApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.google.inject.Module; + +/** + * Implementation of {@link ApiMetadata} for Cinder v1 API + * + * @author Everett Toews + */ +public class CinderApiMetadata extends BaseRestApiMetadata { + + private static final long serialVersionUID = 4525672099385581765L; + + public static final TypeToken> CONTEXT_TOKEN = new TypeToken>() { + private static final long serialVersionUID = -2850937833892503251L; + }; + + @Override + public Builder toBuilder() { + return new Builder().fromApiMetadata(this); + } + + public CinderApiMetadata() { + this(new Builder()); + } + + protected CinderApiMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = BaseRestApiMetadata.defaultProperties(); + + properties.setProperty(SERVICE_TYPE, ServiceType.BLOCK_STORAGE); + properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS); + + return properties; + } + + public static class Builder extends BaseRestApiMetadata.Builder { + + protected Builder() { + super(CinderApi.class, CinderAsyncApi.class); + id("openstack-cinder") + .name("OpenStack Cinder Folsom API") + .identityName("${tenantName}:${userName} or ${userName}, if your keystone supports a default tenant") + .credentialName("${password}") + .endpointName("Keystone base URL ending in /v2.0/") + .documentation(URI.create("http://api.openstack.org/")) + .version("1") + .defaultEndpoint("http://localhost:5000/v2.0/") + .defaultProperties(CinderApiMetadata.defaultProperties()) + .defaultModules(ImmutableSet.>builder() + .add(KeystoneAuthenticationModule.class) + .add(ZoneModule.class) + .add(CinderParserModule.class) + .add(CinderRestClientModule.class) + .build()); + } + + @Override + public CinderApiMetadata build() { + return new CinderApiMetadata(this); + } + + @Override + public Builder fromApiMetadata(ApiMetadata in) { + super.fromApiMetadata(in); + return this; + } + } +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderAsyncApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderAsyncApi.java new file mode 100644 index 0000000000..10f951b4cc --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/CinderAsyncApi.java @@ -0,0 +1,77 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1; + +import java.util.Set; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.location.Zone; +import org.jclouds.location.functions.ZoneToEndpoint; +import org.jclouds.openstack.cinder.v1.features.SnapshotAsyncApi; +import org.jclouds.openstack.cinder.v1.features.VolumeAsyncApi; +import org.jclouds.openstack.cinder.v1.features.VolumeTypeAsyncApi; +import org.jclouds.openstack.v2_0.features.ExtensionAsyncApi; +import org.jclouds.rest.annotations.Delegate; +import org.jclouds.rest.annotations.EndpointParam; + +import com.google.inject.Provides; + +/** + * Provides asynchronous access to Cinder via its REST API. + * + * @see CinderApi + * @see API Doc + * @author Everett Toews + */ +public interface CinderAsyncApi { + /** + * @return the Zone codes configured + */ + @Provides + @Zone + Set getConfiguredZones(); + + /** + * Provides asynchronous access to Extension features. + */ + @Delegate + ExtensionAsyncApi getExtensionApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to Volume features. + */ + @Delegate + VolumeAsyncApi getVolumeApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to VolumeType features. + */ + @Delegate + VolumeTypeAsyncApi getVolumeTypeApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to Snapshot features. + */ + @Delegate + SnapshotAsyncApi getSnapshotApiForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderParserModule.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderParserModule.java new file mode 100644 index 0000000000..adebbefdc3 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderParserModule.java @@ -0,0 +1,34 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.config; + +import org.jclouds.json.config.GsonModule; +import org.jclouds.json.config.GsonModule.DateAdapter; + +import com.google.inject.AbstractModule; + +/** + * @author Everett Toews + */ +public class CinderParserModule extends AbstractModule { + @Override + protected void configure() { + bind(DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class); + } +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderRestClientModule.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderRestClientModule.java new file mode 100644 index 0000000000..b4beeb9978 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/config/CinderRestClientModule.java @@ -0,0 +1,94 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.config; + +import java.net.URI; +import java.util.Map; + +import javax.inject.Singleton; + +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.json.config.GsonModule.DateAdapter; +import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; +import org.jclouds.openstack.cinder.v1.CinderApi; +import org.jclouds.openstack.cinder.v1.CinderAsyncApi; +import org.jclouds.openstack.cinder.v1.features.SnapshotApi; +import org.jclouds.openstack.cinder.v1.features.SnapshotAsyncApi; +import org.jclouds.openstack.cinder.v1.features.VolumeApi; +import org.jclouds.openstack.cinder.v1.features.VolumeAsyncApi; +import org.jclouds.openstack.cinder.v1.features.VolumeTypeApi; +import org.jclouds.openstack.cinder.v1.features.VolumeTypeAsyncApi; +import org.jclouds.openstack.cinder.v1.handlers.CinderErrorHandler; +import org.jclouds.openstack.v2_0.features.ExtensionApi; +import org.jclouds.openstack.v2_0.features.ExtensionAsyncApi; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.rest.config.RestClientModule; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.reflect.TypeToken; +import com.google.inject.Provides; + +/** + * Configures the Cinder connection. + * + * @author Everett Toews + */ +@ConfiguresRestClient +public class CinderRestClientModule extends RestClientModule { + + public static final Map, Class> DELEGATE_MAP = ImmutableMap., Class> builder() + .put(ExtensionApi.class, ExtensionAsyncApi.class) + .put(VolumeApi.class, VolumeAsyncApi.class) + .put(VolumeTypeApi.class, VolumeTypeAsyncApi.class) + .put(SnapshotApi.class, SnapshotAsyncApi.class) + .build(); + + @SuppressWarnings("unchecked") + public CinderRestClientModule() { + super(TypeToken.class.cast(TypeToken.of(CinderApi.class)), TypeToken.class.cast(TypeToken.of(CinderAsyncApi.class)), DELEGATE_MAP); + } + + protected CinderRestClientModule(TypeToken syncClientType, TypeToken asyncClientType, Map, Class> sync2Async) { + super(syncClientType, asyncClientType, sync2Async); + } + + @Override + protected void configure() { + bind(DateAdapter.class).to(Iso8601DateAdapter.class); + super.configure(); + } + + @Provides + @Singleton + public Multimap aliases() { + return ImmutableMultimap.builder().build(); + } + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(CinderErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(CinderErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(CinderErrorHandler.class); + } +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Snapshot.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Snapshot.java new file mode 100644 index 0000000000..e212efe8f4 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Snapshot.java @@ -0,0 +1,244 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Date; + +import javax.inject.Named; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * An Openstack Cinder Volume Snapshot. + * + * @author Everett Toews + */ +public class Snapshot { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromSnapshot(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected String id; + protected String volumeId; + protected Volume.Status status; + protected int size; + protected Date created; + protected String name; + protected String description; + + /** + * @see Snapshot#getId() + */ + public T id(String id) { + this.id = id; + return self(); + } + + /** + * @see Snapshot#getVolumeId() + */ + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + /** + * @see Snapshot#getStatus() + */ + public T status(Volume.Status status) { + this.status = status; + return self(); + } + + /** + * @see Snapshot#getSize() + */ + public T size(int size) { + this.size = size; + return self(); + } + + /** + * @see Snapshot#getCreated() + */ + public T created(Date created) { + this.created = created; + return self(); + } + + /** + * @see Snapshot#getName() + */ + public T name(String name) { + this.name = name; + return self(); + } + + /** + * @see Snapshot#getDescription() + */ + public T description(String description) { + this.description = description; + return self(); + } + + public Snapshot build() { + return new Snapshot(id, volumeId, status, size, created, name, description); + } + + public T fromSnapshot(Snapshot in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .status(in.getStatus()) + .size(in.getSize()) + .created(in.getCreated()) + .name(in.getName()) + .description(in.getDescription()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + @Named("volume_id") + private final String volumeId; + private final Volume.Status status; + private final int size; + @Named("created_at") + private final Date created; + @Named("display_name") + private final String name; + @Named("display_description") + private final String description; + + @ConstructorProperties({ + "id", "volume_id", "status", "size", "created_at", "display_name", "display_description" + }) + protected Snapshot(String id, String volumeId, Volume.Status status, int size, @Nullable Date created, @Nullable String name, @Nullable String description) { + this.id = checkNotNull(id, "id"); + this.volumeId = checkNotNull(volumeId, "volumeId"); + this.status = checkNotNull(status, "status"); + this.size = size; + this.created = created; + this.name = name; + this.description = description; + } + + /** + * @return The id of this snapshot + */ + public String getId() { + return this.id; + } + + /** + * @return The id of the Volume this snapshot was taken from + */ + public String getVolumeId() { + return this.volumeId; + } + + /** + * @return The status of this snapshot + */ + public Volume.Status getStatus() { + return this.status; + } + + /** + * @return The size in GB of the volume this snapshot was taken from + */ + public int getSize() { + return this.size; + } + + /** + * @return The data the snapshot was taken + */ + @Nullable + public Date getCreated() { + return this.created; + } + + /** + * @return The name of this snapshot - as displayed in the openstack console + */ + @Nullable + public String getName() { + return this.name; + } + + /** + * @return The description of this snapshot - as displayed in the openstack console + */ + @Nullable + public String getDescription() { + return this.description; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, status, size, created, name, description); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Snapshot that = Snapshot.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.created, that.created) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this) + .add("id", id).add("volumeId", volumeId).add("status", status).add("size", size).add("created", created).add("name", name).add("description", description); + } + + @Override + public String toString() { + return string().toString(); + } + +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java new file mode 100644 index 0000000000..b280240fd3 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/Volume.java @@ -0,0 +1,352 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import javax.inject.Named; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * An Openstack Cinder Volume. + * + * @author Everett Toews + */ +public class Volume { + + public static enum Status { + CREATING, AVAILABLE, ATTACHING, IN_USE, DELETING, ERROR, ERROR_DELETING, UNRECOGNIZED; + + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + @Override + public String toString() { + return value(); + } + + public static Status fromValue(String status) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, checkNotNull(status, "status"))); + } + catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + } + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVolume(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected String id; + protected Volume.Status status; + protected int size; + protected String zone; + protected Date created; + protected Set attachments = ImmutableSet.of(); + protected String volumeType; + protected String snapshotId; + protected String name; + protected String description; + protected Map metadata = ImmutableMap.of(); + + /** + * @see Volume#getId() + */ + public T id(String id) { + this.id = id; + return self(); + } + + /** + * @see Volume#getStatus() + */ + public T status(Volume.Status status) { + this.status = status; + return self(); + } + + /** + * @see Volume#getSize() + */ + public T size(int size) { + this.size = size; + return self(); + } + + /** + * @see Volume#getZone() + */ + public T zone(String zone) { + this.zone = zone; + return self(); + } + + /** + * @see Volume#getCreated() + */ + public T created(Date created) { + this.created = created; + return self(); + } + + /** + * @see Volume#getAttachments() + */ + public T attachments(Set attachments) { + this.attachments = ImmutableSet.copyOf(checkNotNull(attachments, "attachments")); + return self(); + } + + public T attachments(VolumeAttachment... in) { + return attachments(ImmutableSet.copyOf(in)); + } + + /** + * @see Volume#getVolumeType() + */ + public T volumeType(String volumeType) { + this.volumeType = volumeType; + return self(); + } + + /** + * @see Volume#getSnapshotId() + */ + public T snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return self(); + } + + /** + * @see Volume#getName() + */ + public T name(String name) { + this.name = name; + return self(); + } + + /** + * @see Volume#getDescription() + */ + public T description(String description) { + this.description = description; + return self(); + } + + /** + * @see Volume#getMetadata() + */ + public T metadata(Map metadata) { + this.metadata = ImmutableMap.copyOf(checkNotNull(metadata, "metadata")); + return self(); + } + + public Volume build() { + return new Volume(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); + } + + public T fromVolume(Volume in) { + return this + .id(in.getId()) + .status(in.getStatus()) + .size(in.getSize()) + .zone(in.getZone()) + .created(in.getCreated()) + .attachments(in.getAttachments()) + .volumeType(in.getVolumeType()) + .snapshotId(in.getSnapshotId()) + .name(in.getName()) + .description(in.getDescription()) + .metadata(in.getMetadata()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final Volume.Status status; + private final int size; + @Named("availability_zone") + private final String zone; + @Named("created_at") + private final Date created; + private final Set attachments; + @Named("volume_type") + private final String volumeType; + @Named("snapshot_id") + private final String snapshotId; + @Named("display_name") + private final String name; + @Named("display_description") + private final String description; + private final Map metadata; + + @ConstructorProperties({ + "id", "status", "size", "availability_zone", "created_at", "attachments", "volume_type", "snapshot_id", "display_name", "display_description", "metadata" + }) + protected Volume(String id, Volume.Status status, int size, String zone, Date created, @Nullable Set attachments, @Nullable String volumeType, @Nullable String snapshotId, @Nullable String name, @Nullable String description, @Nullable Map metadata) { + this.id = checkNotNull(id, "id"); + this.status = checkNotNull(status, "status"); + this.size = size; + this.zone = checkNotNull(zone, "zone"); + this.created = checkNotNull(created, "created"); + this.attachments = attachments == null ? ImmutableSet.of() : ImmutableSet.copyOf(attachments); + this.volumeType = volumeType; + this.snapshotId = snapshotId; + this.name = name; + this.description = description; + this.metadata = metadata == null ? ImmutableMap.of() : ImmutableMap.copyOf(metadata); + } + + /** + * @return the id of this volume + */ + public String getId() { + return this.id; + } + + /** + * @return the status of this volume + */ + public Volume.Status getStatus() { + return this.status; + } + + /** + * @return the size in GB of this volume + */ + public int getSize() { + return this.size; + } + + /** + * @return the availabilityZone containing this volume + */ + public String getZone() { + return this.zone; + } + + /** + * @return the time this volume was created + */ + public Date getCreated() { + return this.created; + } + + /** + * @return the set of attachments (to Servers) + */ + public Set getAttachments() { + return this.attachments; + } + + /** + * @return the type of this volume + */ + @Nullable + public String getVolumeType() { + return this.volumeType; + } + + @Nullable + public String getSnapshotId() { + return this.snapshotId; + } + + /** + * @return the name of this volume - as displayed in the openstack console + */ + @Nullable + public String getName() { + return this.name; + } + + /** + * @return the description of this volume - as displayed in the openstack console + */ + @Nullable + public String getDescription() { + return this.description; + } + + public Map getMetadata() { + return this.metadata; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, status, size, zone, created, attachments, volumeType, snapshotId, name, description, metadata); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Volume that = Volume.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.status, that.status) + && Objects.equal(this.size, that.size) + && Objects.equal(this.zone, that.zone) + && Objects.equal(this.created, that.created) + && Objects.equal(this.attachments, that.attachments) + && Objects.equal(this.volumeType, that.volumeType) + && Objects.equal(this.snapshotId, that.snapshotId) + && Objects.equal(this.name, that.name) + && Objects.equal(this.description, that.description) + && Objects.equal(this.metadata, that.metadata); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this) + .add("id", id).add("status", status).add("size", size).add("zone", zone).add("created", created).add("attachments", attachments).add("volumeType", volumeType).add("snapshotId", snapshotId).add("name", name).add("description", description).add("metadata", metadata); + } + + @Override + public String toString() { + return string().toString(); + } + +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeAttachment.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeAttachment.java new file mode 100644 index 0000000000..39e588360c --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeAttachment.java @@ -0,0 +1,178 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; + +import javax.inject.Named; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * An OpenStack Cinder Volume Attachment (describes how Volumes are attached to Servers). + */ +public class VolumeAttachment { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVolumeAttachment(this); + } + + public abstract static class Builder> { + protected abstract T self(); + + protected String id; + protected String volumeId; + protected String serverId; + protected String device; + + /** + * @see VolumeAttachment#getId() + */ + public T id(String id) { + this.id = id; + return self(); + } + + /** + * @see VolumeAttachment#getVolumeId() + */ + public T volumeId(String volumeId) { + this.volumeId = volumeId; + return self(); + } + + /** + * @see VolumeAttachment#getServerId() + */ + public T serverId(String serverId) { + this.serverId = serverId; + return self(); + } + + /** + * @see VolumeAttachment#getDevice() + */ + public T device(String device) { + this.device = device; + return self(); + } + + public VolumeAttachment build() { + return new VolumeAttachment(id, volumeId, serverId, device); + } + + public T fromVolumeAttachment(VolumeAttachment in) { + return this + .id(in.getId()) + .volumeId(in.getVolumeId()) + .serverId(in.getServerId()) + .device(in.getDevice()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + @Named("volume_id") + private final String volumeId; + @Named("server_id") + private final String serverId; + private final String device; + + @ConstructorProperties({ + "id", "volume_id", "server_id", "device" + }) + protected VolumeAttachment(String id, String volumeId, @Nullable String serverId, @Nullable String device) { + this.id = checkNotNull(id, "id"); + this.volumeId = checkNotNull(volumeId, "volumeId"); + this.serverId = serverId; + this.device = device; + } + + /** + * @return the attachment id (typically the same as #getVolumeId()) + */ + public String getId() { + return this.id; + } + + /** + * @return the id of the volume attached + */ + public String getVolumeId() { + return this.volumeId; + } + + /** + * @return the id of the server the volume is attached to + */ + @Nullable + public String getServerId() { + return this.serverId; + } + + /** + * @return the device name (e.g. "/dev/vdc") + */ + @Nullable + public String getDevice() { + return this.device; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, volumeId, serverId, device); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + VolumeAttachment that = VolumeAttachment.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.volumeId, that.volumeId) + && Objects.equal(this.serverId, that.serverId) + && Objects.equal(this.device, that.device); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this) + .add("id", id).add("volumeId", volumeId).add("serverId", serverId).add("device", device); + } + + @Override + public String toString() { + return string().toString(); + } + +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeType.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeType.java new file mode 100644 index 0000000000..0635fbf52b --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/domain/VolumeType.java @@ -0,0 +1,194 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Date; +import java.util.Map; + +import javax.inject.Named; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; + +/** + * An Openstack Cinder Volume Type. + * + * @author Everett Toews + */ +public class VolumeType { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVolumeType(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected String id; + protected String name; + protected Date created; + protected Date updated; + protected Map extraSpecs = ImmutableMap.of(); + + /** + * @see VolumeType#getId() + */ + public T id(String id) { + this.id = id; + return self(); + } + + /** + * @see VolumeType#getName() + */ + public T name(String name) { + this.name = name; + return self(); + } + + /** + * @see VolumeType#getCreated() + */ + public T created(Date created) { + this.created = created; + return self(); + } + + /** + * @see VolumeType#getUpdated() + */ + public T updated(Date updated) { + this.updated = updated; + return self(); + } + + /** + * @see VolumeType#getExtraSpecs() + */ + public T extraSpecs(Map extraSpecs) { + this.extraSpecs = ImmutableMap.copyOf(checkNotNull(extraSpecs, "extraSpecs")); + return self(); + } + + public VolumeType build() { + return new VolumeType(id, name, created, updated, extraSpecs); + } + + public T fromVolumeType(VolumeType in) { + return this + .id(in.getId()) + .name(in.getName()) + .created(in.getCreated().orNull()) + .updated(in.getUpdated().orNull()) + .extraSpecs(in.getExtraSpecs()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + private final String name; + @Named("created_at") + private final Optional created; + @Named("updated_at") + private final Optional updated; + @Named("extra_specs") + private final Map extraSpecs; + + @ConstructorProperties({ + "id", "name", "created_at", "updated_at", "extra_specs" + }) + protected VolumeType(String id, String name, @Nullable Date created, @Nullable Date updated, Map extraSpecs) { + this.id = checkNotNull(id, "id"); + this.name = checkNotNull(name, "name"); + this.created = Optional.fromNullable(created); + this.updated = Optional.fromNullable(updated); + this.extraSpecs = ImmutableMap.copyOf(checkNotNull(extraSpecs, "extraSpecs")); + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + /** + * The Date the VolumeType was created + */ + public Optional getCreated() { + return this.created; + } + + /** + * The Date the VolumeType as last updated - absent if no updates have taken place + */ + public Optional getUpdated() { + return this.updated; + } + + public Map getExtraSpecs() { + return this.extraSpecs; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, created, updated, extraSpecs); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + VolumeType that = VolumeType.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.name, that.name) + && Objects.equal(this.created, that.created) + && Objects.equal(this.updated, that.updated) + && Objects.equal(this.extraSpecs, that.extraSpecs); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this) + .add("id", id).add("name", name).add("created", created).add("updated", updated).add("extraSpecs", extraSpecs); + } + + @Override + public String toString() { + return string().toString(); + } + +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java new file mode 100644 index 0000000000..f18dec140a --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotApi.java @@ -0,0 +1,58 @@ +package org.jclouds.openstack.cinder.v1.features; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.cinder.v1.domain.Snapshot; +import org.jclouds.openstack.cinder.v1.options.CreateSnapshotOptions; + +import com.google.common.collect.FluentIterable; + +/** + * Provides synchronous access to Volume Snapshots via their REST API. + * + * @see SnapshotAsyncApi + * @see API Doc + * @author Everett Toews + */ +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface SnapshotApi { + /** + * Returns a summary list of Snapshots. + * + * @return The list of Snapshots + */ + FluentIterable list(); + + /** + * Returns a detailed list of Snapshots. + * + * @return The list of Snapshots + */ + FluentIterable listInDetail(); + + /** + * Return data about the given Snapshot. + * + * @param snapshotId Id of the Snapshot + * @return Details of a specific Snapshot + */ + Snapshot get(String snapshotId); + + /** + * Creates a new Snapshot + * + * @param volumeId The Volume Id from which to create the Snapshot + * @param options See CreateSnapshotOptions + * @return The new Snapshot + */ + Snapshot create(String volumeId, CreateSnapshotOptions... options); + + /** + * Delete a Snapshot. + * + * @param snapshotId Id of the Snapshot + * @return true if successful, false otherwise + */ + boolean delete(String snapshotId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotAsyncApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotAsyncApi.java new file mode 100644 index 0000000000..33b677cfd2 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/SnapshotAsyncApi.java @@ -0,0 +1,105 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.cinder.v1.domain.Snapshot; +import org.jclouds.openstack.cinder.v1.options.CreateSnapshotOptions; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnEmptyFluentIterableOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to Snapshots. + * + * @see SnapshotApi + * @see API Doc + * @author Everett Toews + */ +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface SnapshotAsyncApi { + /** + * @see SnapshotApi#list() + */ + @GET + @Path("/snapshots") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> list(); + + /** + * @see SnapshotApi#listInDetail() + */ + @GET + @Path("/snapshots/detail") + @SelectJson("snapshots") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> listInDetail(); + + /** + * @see SnapshotApi#get(String) + */ + @GET + @Path("/snapshots/{id}") + @SelectJson("snapshot") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture get(@PathParam("id") String snapshotId); + + /** + * @see SnapshotApi#create(String, CreateSnapshotOptions...) + */ + @POST + @Path("/snapshots") + @SelectJson("snapshot") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @MapBinder(CreateSnapshotOptions.class) + ListenableFuture create(@PayloadParam("volume_id") String volumeId, CreateSnapshotOptions... options); + + /** + * @see SnapshotApi#delete(String) + */ + @DELETE + @Path("/snapshots/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture delete(@PathParam("id") String snapshotId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java new file mode 100644 index 0000000000..de2b84b059 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeApi.java @@ -0,0 +1,61 @@ +package org.jclouds.openstack.cinder.v1.features; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.options.CreateVolumeOptions; + +import com.google.common.collect.FluentIterable; + +/** + * Provides synchronous access to Volumes. + * + * This API strictly handles creating and managing Volumes. To attach a Volume to a Server you need to use the + * @see VolumeAttachmentApi + * + * @see VolumeAsyncApi + * @see API Doc + * @author Everett Toews + */ +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VolumeApi { + /** + * Returns a summary list of Volumes. + * + * @return The list of Volumes + */ + FluentIterable list(); + + /** + * Returns a detailed list of Volumes. + * + * @return The list of Volumes + */ + FluentIterable listInDetail(); + + /** + * Return data about the given Volume. + * + * @param volumeId Id of the Volume + * @return Details of a specific Volume + */ + Volume get(String volumeId); + + /** + * Creates a new Volume + * + * @param volumeId Id of the Volume + * @param options See CreateVolumeOptions + * @return The new Volume + */ + Volume create(int sizeGB, CreateVolumeOptions... options); + + /** + * Delete a Volume. + * + * @param volumeId Id of the Volume + * @return true if successful, false otherwise + */ + boolean delete(String volumeId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeAsyncApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeAsyncApi.java new file mode 100644 index 0000000000..1fcdc079fc --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeAsyncApi.java @@ -0,0 +1,105 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.options.CreateVolumeOptions; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnEmptyFluentIterableOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to Volumes. + * + * @see VolumeApi + * @see API Doc + * @author Everett Toews + */ +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VolumeAsyncApi { + /** + * @see VolumeApi#list() + */ + @GET + @Path("/volumes") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> list(); + + /** + * @see VolumeApi#listInDetail() + */ + @GET + @Path("/volumes/detail") + @SelectJson("volumes") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> listInDetail(); + + /** + * @see VolumeApi#get(String) + */ + @GET + @Path("/volumes/{id}") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture get(@PathParam("id") String volumeId); + + /** + * @see VolumeApi#create(int, CreateVolumeOptions...) + */ + @POST + @Path("/volumes") + @SelectJson("volume") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @MapBinder(CreateVolumeOptions.class) + ListenableFuture create(@PayloadParam("size") int sizeGB, CreateVolumeOptions... options); + + /** + * @see VolumeApi#delete(String) + */ + @DELETE + @Path("/volumes/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture delete(@PathParam("id") String volumeId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApi.java new file mode 100644 index 0000000000..7e8f8efb15 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApi.java @@ -0,0 +1,33 @@ +package org.jclouds.openstack.cinder.v1.features; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.cinder.v1.domain.VolumeType; + +import com.google.common.collect.FluentIterable; + +/** + * Provides synchronous access to Volumes via their REST API. + * + * @see VolumeAsyncApi + * @see API Doc + * @author Everett Toews + */ +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VolumeTypeApi { + /** + * Returns a summary list of VolumeTypes. + * + * @return The list of VolumeTypes + */ + FluentIterable list(); + + /** + * Return data about the given VolumeType. + * + * @param volumeTypeId Id of the VolumeType + * @return Details of a specific VolumeType + */ + VolumeType get(String volumeTypeId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeAsyncApi.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeAsyncApi.java new file mode 100644 index 0000000000..77bb86d7c4 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeAsyncApi.java @@ -0,0 +1,68 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.cinder.v1.domain.VolumeType; +import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnEmptyFluentIterableOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to VolumeTypes. + * + * @see VolumeTypeApi + * @see API Doc + * @author Everett Toews + */ +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VolumeTypeAsyncApi { + /** + * @see VolumeTypeApi#list() + */ + @GET + @Path("/types") + @SelectJson("volume_types") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnEmptyFluentIterableOnNotFoundOr404.class) + ListenableFuture> list(); + + /** + * @see VolumeTypeApi#get(String) + */ + @GET + @Path("/types/{id}") + @SelectJson("volume_type") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture get(@PathParam("id") String volumeTypeId); +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/handlers/CinderErrorHandler.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/handlers/CinderErrorHandler.java new file mode 100644 index 0000000000..acc10d060d --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/handlers/CinderErrorHandler.java @@ -0,0 +1,73 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.handlers; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; + +import javax.inject.Singleton; + +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.InsufficientResourcesException; +import org.jclouds.rest.ResourceNotFoundException; + +/** + * This will parse and set an appropriate exception on the command object. + * + * @author Everett Toews + * + */ +@Singleton +public class CinderErrorHandler implements HttpErrorHandler { + + 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 400: + if (message.contains("quota exceeded")) + exception = new InsufficientResourcesException(message, exception); + else if (message.contains("Invalid volume")) + exception = new IllegalStateException(message, exception); + break; + case 401: + case 403: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + case 413: + exception = new InsufficientResourcesException(message, exception); + break; + } + command.setException(exception); + } +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateSnapshotOptions.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateSnapshotOptions.java new file mode 100644 index 0000000000..dbbd5cb749 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateSnapshotOptions.java @@ -0,0 +1,140 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; + +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * @author Adam Lowe + */ +public class CreateSnapshotOptions implements MapBinder { + public static final CreateSnapshotOptions NONE = new CreateSnapshotOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + private String name; + private String description; + private boolean force = false; + + @Override + public R bindToRequest(R request, Map postParams) { + Map data = Maps.newHashMap(postParams); + if (name != null) + data.put("display_name", name); + if (description != null) + data.put("display_description", description); + if (force) + data.put("force", "true"); + return jsonBinder.bindToRequest(request, ImmutableMap.of("snapshot", data)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateSnapshot is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateSnapshotOptions)) return false; + final CreateSnapshotOptions other = CreateSnapshotOptions.class.cast(object); + return equal(name, other.name) && equal(description, other.description); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, description); + } + + protected ToStringHelper string() { + return toStringHelper("").add("name", name).add("description", description); + } + + @Override + public String toString() { + return string().toString(); + } + + public CreateSnapshotOptions name(String name) { + this.name = name; + return this; + } + + public CreateSnapshotOptions description(String description) { + this.description = description; + return this; + } + + public CreateSnapshotOptions force() { + this.force = true; + return this; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean isForce() { + return force; + } + + public static class Builder { + /** + * @see CreateSnapshotOptions#getName() + */ + public static CreateSnapshotOptions name(String name) { + return new CreateSnapshotOptions().name(name); + } + /** + * @see CreateSnapshotOptions#getDescription() + */ + public static CreateSnapshotOptions description(String description) { + return new CreateSnapshotOptions().description(description); + } + + /** + * @see CreateSnapshotOptions#isForce() + */ + public static CreateSnapshotOptions force() { + return new CreateSnapshotOptions().force(); + } + } + +} diff --git a/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateVolumeOptions.java b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateVolumeOptions.java new file mode 100644 index 0000000000..b56c682f77 --- /dev/null +++ b/labs/openstack-cinder/src/main/java/org/jclouds/openstack/cinder/v1/options/CreateVolumeOptions.java @@ -0,0 +1,240 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.options; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +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 javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.openstack.cinder.v1.features.VolumeTypeApi; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * @author Adam Lowe + */ +public class CreateVolumeOptions implements MapBinder { + public static final CreateVolumeOptions NONE = new CreateVolumeOptions(); + + @Inject + private BindToJsonPayload jsonBinder; + + protected String name; + protected String description; + protected String volumeType; + protected String availabilityZone; + protected String snapshotId; + protected Map metadata = ImmutableMap.of(); + + @Override + public R bindToRequest(R request, Map postParams) { + Map image = Maps.newHashMap(); + image.putAll(postParams); + if (name != null) + image.put("display_name", name); + if (description != null) + image.put("display_description", description); + if (volumeType != null) + image.put("volume_type", volumeType); + if (availabilityZone != null) + image.put("availability_zone", availabilityZone); + if (snapshotId != null) + image.put("snapshot_id", snapshotId); + if (!metadata.isEmpty()) + image.put("metadata", metadata); + return jsonBinder.bindToRequest(request, ImmutableMap.of("volume", image)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("CreateVolume is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateVolumeOptions)) return false; + final CreateVolumeOptions other = CreateVolumeOptions.class.cast(object); + return equal(volumeType, other.volumeType) && equal(availabilityZone, other.availabilityZone) && equal(snapshotId, other.snapshotId) + && equal(name, other.name) && equal(description, other.description) && equal(metadata, other.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(volumeType, availabilityZone, snapshotId, name, description, metadata); + } + + protected ToStringHelper string() { + return toStringHelper("").add("volumeType", volumeType).add("availabilityZone", availabilityZone) + .add("snapshotId", snapshotId).add("name", name).add("description", description).add("metadata", metadata); + } + + @Override + public String toString() { + return string().toString(); + } + + /** + * Custom cloud server metadata can also be supplied at launch time. This + * metadata is stored in the API system where it is retrievable by querying + * the API for server status. The maximum size of the metadata key and value + * is each 255 bytes and the maximum number of key-value pairs that can be + * supplied per volume is 5. + */ + public CreateVolumeOptions metadata(Map metadata) { + checkNotNull(metadata, "metadata"); + checkArgument(metadata.size() <= 5, + "you cannot have more then 5 metadata values. You specified: " + metadata.size()); + for (Entry entry : metadata.entrySet()) { + checkArgument( + entry.getKey().getBytes().length < 255, + String.format("maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", + entry.getKey(), entry.getKey().getBytes().length)); + checkArgument(entry.getKey().getBytes().length < 255, String.format( + "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", + entry.getKey(), entry.getValue(), entry.getValue().getBytes().length)); + } + this.metadata = ImmutableMap.copyOf(metadata); + return this; + } + + /** + * @param name The name of the Volume + */ + public CreateVolumeOptions name(String name) { + this.name = name; + return this; + } + + /** + * @param description A description of the Volume + */ + public CreateVolumeOptions description(String description) { + this.description = description; + return this; + } + + /** + * @see VolumeTypeApi#list() + * + * @param volumeType The type of Volume to create + */ + public CreateVolumeOptions volumeType(String volumeType) { + this.volumeType = volumeType; + return this; + } + + /** + * @param availabilityZone The optional availability zone in which to create a Volume + */ + public CreateVolumeOptions availabilityZone(String availabilityZone) { + this.availabilityZone = availabilityZone; + return this; + } + + /** + * @param snapshotId The optional snapshot from which to create a Volume + */ + public CreateVolumeOptions snapshotId(String snapshotId) { + this.snapshotId = snapshotId; + return this; + } + + public String getVolumeType() { + return volumeType; + } + + public String getAvailabilityZone() { + return availabilityZone; + } + + public String getSnapshotId() { + return snapshotId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Map getMetadata() { + return metadata; + } + + public static class Builder { + /** + * @see CreateVolumeOptions#getName() + */ + public static CreateVolumeOptions name(String name) { + return new CreateVolumeOptions().name(name); + } + /** + * @see CreateVolumeOptions#getDescription() + */ + public static CreateVolumeOptions description(String description) { + return new CreateVolumeOptions().description(description); + } + + /** + * @see CreateVolumeOptions#getVolumeType() + */ + public static CreateVolumeOptions volumeType(String volumeType) { + return new CreateVolumeOptions().volumeType(volumeType); + } + + /** + * @see CreateVolumeOptions#getAvailabilityZone() + */ + public static CreateVolumeOptions availabilityZone(String availabilityZone) { + return new CreateVolumeOptions().availabilityZone(availabilityZone); + } + + /** + * @see CreateVolumeOptions#getSnapshotId() + */ + public static CreateVolumeOptions snapshotId(String snapshotId) { + return new CreateVolumeOptions().snapshotId(snapshotId); + } + + /** + * @see CreateVolumeOptions#getMetadata() + */ + public static CreateVolumeOptions metadata(Map metadata) { + return new CreateVolumeOptions().metadata(metadata); + } + } + +} diff --git a/labs/openstack-cinder/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/labs/openstack-cinder/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000000..b007ce5e30 --- /dev/null +++ b/labs/openstack-cinder/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.openstack.cinder.v1.CinderApiMetadata \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/SnapshotApiExpectTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/SnapshotApiExpectTest.java new file mode 100644 index 0000000000..b42c27e801 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/SnapshotApiExpectTest.java @@ -0,0 +1,303 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.net.URI; +import java.util.Set; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.cinder.v1.domain.Snapshot; +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiExpectTest; +import org.jclouds.openstack.cinder.v1.options.CreateSnapshotOptions; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests SnapshotApi Guice wiring and parsing + * + * @author Everett Toews + */ +@Test(groups = "unit", testName = "SnapshotApiExpectTest") +public class SnapshotApiExpectTest extends BaseCinderApiExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListSnapshots() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/snapshot_list_simple.json")).build() + ).getSnapshotApiForZone("RegionOne"); + + Set snapshots = api.list().toImmutableSet(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + } + + public void testListSnapshotsFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getSnapshotApiForZone("RegionOne"); + + Set snapshots = api.list().toImmutableSet(); + assertTrue(snapshots.isEmpty()); + } + + public void testListSnapshotsInDetail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/detail"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/snapshot_list_details.json")).build() + ).getSnapshotApiForZone("RegionOne"); + + Set snapshots = api.listInDetail().toImmutableSet(); + assertEquals(snapshots, ImmutableSet.of(testSnapshot())); + + // double-check individual fields + Snapshot snappy = Iterables.getOnlyElement(snapshots); + assertEquals(snappy.getId(), "67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + assertEquals(snappy.getVolumeId(), "ea6f70ef-2784-40b9-9d14-d7f33c507c3f"); + assertEquals(snappy.getStatus(), Volume.Status.AVAILABLE); + assertEquals(snappy.getDescription(), "jclouds test snapshot"); + assertEquals(snappy.getName(), "jclouds-test-snapshot"); + assertEquals(snappy.getSize(), 1); + } + + public void testListSnapshotsInDetailFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/detail"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getSnapshotApiForZone("RegionOne"); + + Set snapshots = api.listInDetail().toImmutableSet(); + assertTrue(snapshots.isEmpty()); + } + + public void testGetSnapshot() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/snapshot_get.json")).build() + ).getSnapshotApiForZone("RegionOne"); + + Snapshot snapshot = api.get("67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + assertEquals(snapshot, testSnapshot()); + } + + public void testGetSnapshotFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getSnapshotApiForZone("RegionOne"); + + assertNull(api.get("67d03df1-ce5d-4ba7-adbe-492ceb80170b")); + } + + public void testCreateSnapshot() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/snapshot_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/snapshot_create_response.json")).build() + ).getSnapshotApiForZone("RegionOne"); + + CreateSnapshotOptions options = CreateSnapshotOptions.Builder + .name("jclouds-test-snapshot") + .description("jclouds test snapshot") + .force(); + + Snapshot snapshot = api.create("ea6f70ef-2784-40b9-9d14-d7f33c507c3f", options); + assertEquals(snapshot, testSnapshotCreate()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testCreateSnapshotVolumeNotFoundFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/snapshot_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder().statusCode(404).build() + ).getSnapshotApiForZone("RegionOne"); + + CreateSnapshotOptions options = CreateSnapshotOptions.Builder + .name("jclouds-test-snapshot") + .description("jclouds test snapshot") + .force(); + + api.create("ea6f70ef-2784-40b9-9d14-d7f33c507c3f", options); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCreateSnapshotVolumeIllegalStateFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/snapshot_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder() + .statusCode(400) + .payload("{\"badRequest\": {\"message\": \"Invalid volume: must be available\", \"code\": 400}}") + .build() + ).getSnapshotApiForZone("RegionOne"); + + CreateSnapshotOptions options = CreateSnapshotOptions.Builder + .name("jclouds-test-snapshot") + .description("jclouds test snapshot") + .force(); + + api.create("ea6f70ef-2784-40b9-9d14-d7f33c507c3f", options); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testCreateSnapshotFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/snapshot_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder().statusCode(401).build() + ).getSnapshotApiForZone("RegionOne"); + + CreateSnapshotOptions options = CreateSnapshotOptions.Builder + .name("jclouds-test-snapshot") + .description("jclouds test snapshot") + .force(); + + api.create("ea6f70ef-2784-40b9-9d14-d7f33c507c3f", options); + } + + public void testDeleteSnapshot() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(200).build() + ).getSnapshotApiForZone("RegionOne"); + + assertTrue(api.delete("67d03df1-ce5d-4ba7-adbe-492ceb80170b")); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testDeleteSnapshotFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(401).build() + ).getSnapshotApiForZone("RegionOne"); + + api.delete("67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + } + + public void testDeleteSnapshotNotFoundFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(404).build() + ).getSnapshotApiForZone("RegionOne"); + + assertFalse(api.delete("67d03df1-ce5d-4ba7-adbe-492ceb80170b")); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testDeleteSnapshotIllegalStateFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/snapshots/67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + SnapshotApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder() + .statusCode(400) + .payload("{\"badRequest\": {\"message\": \"Invalid volume: Volume Snapshot status must be available or error\", \"code\": 400}}") + .build() + ).getSnapshotApiForZone("RegionOne"); + + api.delete("67d03df1-ce5d-4ba7-adbe-492ceb80170b"); + } + + protected Snapshot testSnapshotCreate() { + return Snapshot.builder() + .id("67d03df1-ce5d-4ba7-adbe-492ceb80170b") + .volumeId("ea6f70ef-2784-40b9-9d14-d7f33c507c3f") + .description("jclouds test snapshot") + .status(Volume.Status.CREATING) + .name("jclouds-test-snapshot") + .size(1) + .created(dateService.iso8601DateParse("2012-11-02T16:23:27.000000")) + .build(); + } + + protected Snapshot testSnapshot() { + return Snapshot.builder() + .id("67d03df1-ce5d-4ba7-adbe-492ceb80170b") + .volumeId("ea6f70ef-2784-40b9-9d14-d7f33c507c3f") + .description("jclouds test snapshot") + .status(Volume.Status.AVAILABLE) + .name("jclouds-test-snapshot") + .size(1) + .created(dateService.iso8601DateParse("2012-11-02T16:23:27.000000")) + .build(); + } +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java new file mode 100644 index 0000000000..c203a87211 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeAndSnapshotApiLiveTest.java @@ -0,0 +1,210 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Set; + +import org.jclouds.openstack.cinder.v1.domain.Snapshot; +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiLiveTest; +import org.jclouds.openstack.cinder.v1.options.CreateSnapshotOptions; +import org.jclouds.openstack.cinder.v1.options.CreateVolumeOptions; +import org.jclouds.predicates.RetryablePredicate; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of VolumeApi + * + * @author Everett Toews + */ +@Test(groups = "live", testName = "VolumeApiLiveTest", singleThreaded = true) +public class VolumeAndSnapshotApiLiveTest extends BaseCinderApiLiveTest { + private static final String name = System.getProperty("user.name").replace('.','-').toLowerCase(); + + private String zone; + + private VolumeApi volumeApi; + private SnapshotApi snapshotApi; + + private Volume testVolume; + private Snapshot testSnapshot; + + @BeforeClass(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(cinder.getApi().getConfiguredZones(), "nova"); + volumeApi = cinder.getApi().getVolumeApiForZone(zone); + snapshotApi = cinder.getApi().getSnapshotApiForZone(zone); + } + + @AfterClass(groups = { "integration", "live" }) + @Override + protected void tearDownContext() { + if (testSnapshot != null) { + assertTrue(snapshotApi.delete(testSnapshot.getId())); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(SnapshotApi snapshotApi) { + return snapshotApi.get(testSnapshot.getId()) == null; + } + }, 30 * 1000L).apply(snapshotApi)); + } + + if (testVolume != null) { + assertTrue(volumeApi.delete(testVolume.getId())); + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeApi volumeApi) { + return volumeApi.get(testVolume.getId()) == null; + } + }, 30 * 1000L).apply(volumeApi)); + } + + super.tearDownContext(); + } + + public void testCreateVolume() { + CreateVolumeOptions options = CreateVolumeOptions.Builder + .name(name) + .description("description of test volume") + .availabilityZone(zone); + testVolume = volumeApi.create(100, options); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeApi volumeApi) { + return volumeApi.get(testVolume.getId()).getStatus() == Volume.Status.AVAILABLE; + } + }, 600 * 1000L).apply(volumeApi)); + } + + @Test(dependsOnMethods = "testCreateVolume") + public void testListVolumes() { + Set volumes = volumeApi.list().toImmutableSet(); + assertNotNull(volumes); + boolean foundIt = false; + for (Volume vol : volumes) { + Volume details = volumeApi.get(vol.getId()); + assertNotNull(details); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } + } + assertTrue(foundIt, "Failed to find the volume we created in list() response"); + } + + @Test(dependsOnMethods = "testCreateVolume") + public void testListVolumesInDetail() { + Set volumes = volumeApi.listInDetail().toImmutableSet(); + assertNotNull(volumes); + boolean foundIt = false; + for (Volume vol : volumes) { + Volume details = volumeApi.get(vol.getId()); + assertNotNull(details); + assertNotNull(details.getId()); + assertNotNull(details.getCreated()); + assertTrue(details.getSize() > -1); + + assertEquals(details.getId(), vol.getId()); + assertEquals(details.getSize(), vol.getSize()); + assertEquals(details.getName(), vol.getName()); + assertEquals(details.getDescription(), vol.getDescription()); + assertEquals(details.getCreated(), vol.getCreated()); + if (Objects.equal(details.getId(), testVolume.getId())) { + foundIt = true; + } + } + assertTrue(foundIt, "Failed to find the volume we previously created in listInDetail() response"); + } + + @Test(dependsOnMethods = "testCreateVolume") + public void testCreateSnapshot() { + testSnapshot = snapshotApi.create( + testVolume.getId(), + CreateSnapshotOptions.Builder.name("jclouds-live-test").description( + "jclouds live test snapshot").force()); + assertNotNull(testSnapshot); + assertNotNull(testSnapshot.getId()); + final String snapshotId = testSnapshot.getId(); + assertNotNull(testSnapshot.getStatus()); + assertTrue(testSnapshot.getSize() > -1); + assertNotNull(testSnapshot.getCreated()); + + assertTrue(new RetryablePredicate(new Predicate() { + @Override + public boolean apply(VolumeApi volumeApi) { + return snapshotApi.get(snapshotId).getStatus() == Volume.Status.AVAILABLE; + } + }, 1200 * 1000L).apply(volumeApi)); + } + + @Test(dependsOnMethods = "testCreateSnapshot") + public void testListSnapshots() { + Set snapshots = snapshotApi.list().toImmutableSet(); + assertNotNull(snapshots); + boolean foundIt = false; + for (Snapshot snap : snapshots) { + Snapshot details = snapshotApi.get(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + } + assertNotNull(details); + assertEquals(details.getId(), snap.getId()); + assertEquals(details.getVolumeId(), snap.getVolumeId()); + } + assertTrue(foundIt, "Failed to find the snapshot we previously created in listSnapshots() response"); + } + + @Test(dependsOnMethods = "testCreateSnapshot") + public void testListSnapshotsInDetail() { + Set snapshots = snapshotApi.listInDetail().toImmutableSet(); + assertNotNull(snapshots); + boolean foundIt = false; + for (Snapshot snap : snapshots) { + Snapshot details = snapshotApi.get(snap.getId()); + if (Objects.equal(snap.getVolumeId(), testVolume.getId())) { + foundIt = true; + assertSame(details, testSnapshot); + } + assertSame(details, snap); + } + + assertTrue(foundIt, "Failed to find the snapshot we created in listSnapshotsInDetail() response"); + } + + private void assertSame(Snapshot a, Snapshot b) { + assertNotNull(a); + assertNotNull(b); + assertEquals(a.getId(), b.getId()); + assertEquals(a.getDescription(), b.getDescription()); + assertEquals(a.getName(), b.getName()); + assertEquals(a.getVolumeId(), b.getVolumeId()); + } +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeApiExpectTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeApiExpectTest.java new file mode 100644 index 0000000000..bf17c4d7a9 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeApiExpectTest.java @@ -0,0 +1,234 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.net.URI; +import java.util.Set; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.cinder.v1.domain.Volume; +import org.jclouds.openstack.cinder.v1.domain.VolumeAttachment; +import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiExpectTest; +import org.jclouds.openstack.cinder.v1.options.CreateVolumeOptions; +import org.jclouds.rest.ResourceNotFoundException; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests VolumeApi Guice wiring and parsing + * + * @author Everett Toews + */ +@Test(groups = "unit", testName = "VolumeApiExpectTest") +public class VolumeApiExpectTest extends BaseCinderApiExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListVolumes() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_simple.json")).build() + ).getVolumeApiForZone("RegionOne"); + + Set volumes = api.list().toImmutableSet(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + public void testListVolumesFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeApiForZone("RegionOne"); + + Set volumes = api.list().toImmutableSet(); + assertTrue(volumes.isEmpty()); + } + + public void testListVolumesInDetail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/detail"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_list_details.json")).build() + ).getVolumeApiForZone("RegionOne"); + + Set volumes = api.listInDetail().toImmutableSet(); + assertEquals(volumes, ImmutableSet.of(testVolume())); + } + + public void testListVolumesInDetailFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/detail"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeApiForZone("RegionOne"); + + Set volumes = api.listInDetail().toImmutableSet(); + assertTrue(volumes.isEmpty()); + } + + public void testCreateVolume() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/volume_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_create_response.json")).build() + ).getVolumeApiForZone("RegionOne"); + + CreateVolumeOptions options = CreateVolumeOptions.Builder + .name("jclouds-test-volume") + .description("description of test volume"); + Volume volume = api.create(1, options); + assertEquals(volume, testVolumeCreate()); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testCreateVolumeFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint) + .endpoint(endpoint) + .method("POST") + .payload(payloadFromResourceWithContentType("/volume_create.json", MediaType.APPLICATION_JSON)) + .build(), + HttpResponse.builder().statusCode(404).payload(payloadFromResource("/volume_create_response.json")).build() + ).getVolumeApiForZone("RegionOne"); + + CreateVolumeOptions options = CreateVolumeOptions.Builder + .name("jclouds-test-volume") + .description("description of test volume"); + api.create(1, options); + } + + public void testGetVolume() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/60761c60-0f56-4499-b522-ff13e120af10"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_get.json")).build() + ).getVolumeApiForZone("RegionOne"); + + Volume volume = api.get("60761c60-0f56-4499-b522-ff13e120af10"); + assertEquals(volume, testVolume()); + // double-check equals() + assertEquals(volume.getName(), "test"); + assertEquals(volume.getZone(), "nova"); + assertEquals(volume.getStatus(), Volume.Status.IN_USE); + assertEquals(volume.getDescription(), "This is a test volume"); + assertEquals(Iterables.getOnlyElement(volume.getAttachments()), testAttachment()); + } + + public void testGetVolumeFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/60761c60-0f56-4499-b522-ff13e120af10"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeApiForZone("RegionOne"); + + assertNull(api.get("60761c60-0f56-4499-b522-ff13e120af10")); + } + + public void testDeleteVolume() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/60761c60-0f56-4499-b522-ff13e120af10"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(202).build() + ).getVolumeApiForZone("RegionOne"); + + assertTrue(api.delete("60761c60-0f56-4499-b522-ff13e120af10")); + } + + public void testDeleteVolumeFail() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/volumes/60761c60-0f56-4499-b522-ff13e120af10"); + VolumeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).method("DELETE").build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeApiForZone("RegionOne"); + + assertFalse(api.delete("60761c60-0f56-4499-b522-ff13e120af10")); + } + + protected Volume testVolumeCreate() { + return Volume.builder() + .id("60761c60-0f56-4499-b522-ff13e120af10") + .size(1) + .name("jclouds-test-volume") + .zone("nova") + .status(Volume.Status.CREATING) + .volumeType("None") + .description("description of test volume") + .created(dateService.iso8601DateParse("2012-10-29T20:53:28.000000")) + .build(); + } + + protected Volume testVolume() { + return Volume.builder() + .id("60761c60-0f56-4499-b522-ff13e120af10") + .size(1) + .name("test") + .zone("nova") + .status(Volume.Status.IN_USE) + .volumeType("None") + .description("This is a test volume") + .attachments(ImmutableSet.of(testAttachment())) + .created(dateService.iso8601DateParse("2012-10-29T20:53:28.000000")) + .build(); + } + + protected VolumeAttachment testAttachment() { + return VolumeAttachment.builder() + .id("60761c60-0f56-4499-b522-ff13e120af10") + .volumeId("60761c60-0f56-4499-b522-ff13e120af10") + .serverId("0229a1c1-d54a-4836-8527-2ab28b42e2bb") + .device("/dev/vdc") + .build(); + } +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiExpectTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiExpectTest.java new file mode 100644 index 0000000000..5adb19e3a0 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiExpectTest.java @@ -0,0 +1,92 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +import java.net.URI; +import java.util.Set; + +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.cinder.v1.domain.VolumeType; +import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiExpectTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * Tests Guice wiring and parsing of VolumeTypeApi + * + * @author Everett Toews + */ +@Test(groups = "unit", testName = "VolumeTypeApiExpectTest") +public class VolumeTypeApiExpectTest extends BaseCinderApiExpectTest { + private DateService dateService = new SimpleDateFormatDateService(); + + public void testListVolumeTypes() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/types"); + VolumeTypeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_type_list_simple.json")).build() + ).getVolumeTypeApiForZone("RegionOne"); + + Set types = api.list().toImmutableSet(); + assertEquals(types, ImmutableSet.of(testVolumeType())); + } + + public void testGetVolumeType() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/types/1"); + VolumeTypeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/volume_type_get.json")).build() + ).getVolumeTypeApiForZone("RegionOne"); + + VolumeType type = api.get("1"); + assertEquals(type, testVolumeType()); + } + + public void testGetVolumeTypeFailNotFound() { + URI endpoint = URI.create("http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d/types/X"); + VolumeTypeApi api = requestsSendResponses( + keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(404).build() + ).getVolumeTypeApiForZone("RegionOne"); + + assertNull(api.get("X")); + } + + public VolumeType testVolumeType() { + return VolumeType.builder() + .id("1") + .name("jclouds-test-1") + .created(dateService.iso8601SecondsDateParse("2012-05-10 12:33:06")) + .extraSpecs(ImmutableMap.of("test", "value1", "test1", "wibble")) + .build(); + } +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiLiveTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiLiveTest.java new file mode 100644 index 0000000000..293ae59976 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/features/VolumeTypeApiLiveTest.java @@ -0,0 +1,66 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.features; + +import static org.testng.Assert.assertNotNull; + +import java.util.Set; + +import org.jclouds.openstack.cinder.v1.domain.VolumeType; +import org.jclouds.openstack.cinder.v1.internal.BaseCinderApiLiveTest; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +/** + * Tests behavior of VolumeTypeApi. + * + * @author Everett Toews + */ +@Test(groups = "live", testName = "VolumeTypeApiLiveTest", singleThreaded = true) +public class VolumeTypeApiLiveTest extends BaseCinderApiLiveTest { + private VolumeTypeApi volumeTypeApi; + private String zone; + + @BeforeGroups(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(cinder.getApi().getConfiguredZones(), "nova"); + volumeTypeApi = cinder.getApi().getVolumeTypeApiForZone(zone); + } + + @AfterClass(groups = { "integration", "live" }) + @Override + protected void tearDownContext() { + super.tearDownContext(); + } + + public void testListAndGetVolumeTypes() { + Set volumeTypes = volumeTypeApi.list().toImmutableSet(); + assertNotNull(volumeTypes); + + for (VolumeType vt : volumeTypes) { + VolumeType details = volumeTypeApi.get(vt.getId()); + assertNotNull(details); + } + } +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiExpectTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiExpectTest.java new file mode 100644 index 0000000000..0286a5474b --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiExpectTest.java @@ -0,0 +1,29 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.internal; + +import org.jclouds.openstack.cinder.v1.CinderApi; + +/** + * Base class for writing Volume Rest Api Expect tests + * + * @author Everett Toews + */ +public class BaseCinderApiExpectTest extends BaseCinderExpectTest { +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiLiveTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiLiveTest.java new file mode 100644 index 0000000000..f7f852ab03 --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderApiLiveTest.java @@ -0,0 +1,65 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.internal; + +import java.util.Properties; + +import org.jclouds.apis.BaseContextLiveTest; +import org.jclouds.openstack.cinder.v1.CinderApi; +import org.jclouds.openstack.cinder.v1.CinderApiMetadata; +import org.jclouds.openstack.cinder.v1.CinderAsyncApi; +import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; +import org.jclouds.rest.RestContext; +import org.testng.annotations.BeforeGroups; + +import com.google.common.reflect.TypeToken; + +/** + * Tests behavior of CinderApi + * + * @author Everett Toews + */ +public class BaseCinderApiLiveTest extends BaseContextLiveTest> { + + public BaseCinderApiLiveTest() { + provider = "openstack-cinder"; + } + + protected RestContext cinder; + + @BeforeGroups(groups = { "integration", "live" }) + @Override + public void setupContext() { + super.setupContext(); + cinder = context; + } + + @Override + protected Properties setupProperties() { + Properties props = super.setupProperties(); + setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE); + return props; + } + + @Override + protected TypeToken> contextType() { + return CinderApiMetadata.CONTEXT_TOKEN; + } + +} diff --git a/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderExpectTest.java b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderExpectTest.java new file mode 100644 index 0000000000..35629d13dc --- /dev/null +++ b/labs/openstack-cinder/src/test/java/org/jclouds/openstack/cinder/v1/internal/BaseCinderExpectTest.java @@ -0,0 +1,74 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.cinder.v1.internal; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture; +import org.jclouds.rest.internal.BaseRestApiExpectTest; + +/** + * Base class for writing Nova Expect tests + * + * @author Adrian Cole + */ +public class BaseCinderExpectTest extends BaseRestApiExpectTest { + protected HttpRequest keystoneAuthWithUsernameAndPassword; + protected HttpRequest keystoneAuthWithUsernameAndPasswordAndTenantName; + protected HttpRequest keystoneAuthWithAccessKeyAndSecretKeyAndTenantName; + protected String authToken; + protected HttpResponse responseWithKeystoneAccess; + protected HttpRequest extensionsOfNovaRequest; + protected HttpResponse extensionsOfNovaResponse; + protected HttpResponse unmatchedExtensionsOfNovaResponse; + protected HttpRequest keystoneAuthWithAccessKeyAndSecretKeyAndTenantId; + protected String identityWithTenantId; + + public BaseCinderExpectTest() { + provider = "openstack-cinder"; + keystoneAuthWithUsernameAndPassword = KeystoneFixture.INSTANCE.initialAuthWithUsernameAndPassword(identity, + credential); + keystoneAuthWithUsernameAndPasswordAndTenantName = KeystoneFixture.INSTANCE.initialAuthWithUsernameAndPasswordAndTenantName(identity, + credential); + keystoneAuthWithAccessKeyAndSecretKeyAndTenantName = KeystoneFixture.INSTANCE.initialAuthWithAccessKeyAndSecretKeyAndTenantName(identity, + credential); + keystoneAuthWithAccessKeyAndSecretKeyAndTenantId = KeystoneFixture.INSTANCE.initialAuthWithAccessKeyAndSecretKeyAndTenantId(identity, + credential); + + authToken = KeystoneFixture.INSTANCE.getAuthToken(); + responseWithKeystoneAccess = KeystoneFixture.INSTANCE.responseWithAccess(); + // now, createContext arg will need tenant prefix + identityWithTenantId = KeystoneFixture.INSTANCE.getTenantId() + ":" + identity; + identity = KeystoneFixture.INSTANCE.getTenantName() + ":" + identity; + } + + @Override + protected HttpRequestComparisonType compareHttpRequestAsType(HttpRequest input) { + return HttpRequestComparisonType.JSON; + } + + protected HttpRequest.Builder authenticatedGET() { + return HttpRequest.builder() + .method("GET") + .addHeader("Accept", MediaType.APPLICATION_JSON) + .addHeader("X-Auth-Token", authToken); + } +} diff --git a/labs/openstack-cinder/src/test/resources/snapshot_create.json b/labs/openstack-cinder/src/test/resources/snapshot_create.json new file mode 100644 index 0000000000..66b3303c20 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/snapshot_create.json @@ -0,0 +1 @@ +{"snapshot":{"display_name":"jclouds-test-snapshot","volume_id":"ea6f70ef-2784-40b9-9d14-d7f33c507c3f","display_description":"jclouds test snapshot","force":"true"}} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/snapshot_create_response.json b/labs/openstack-cinder/src/test/resources/snapshot_create_response.json new file mode 100644 index 0000000000..9aba94a0fe --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/snapshot_create_response.json @@ -0,0 +1,11 @@ +{ + "snapshot": { + "status": "creating", + "display_name": "jclouds-test-snapshot", + "created_at": "2012-11-02T16:23:27.000000", + "display_description": "jclouds test snapshot", + "volume_id": "ea6f70ef-2784-40b9-9d14-d7f33c507c3f", + "id": "67d03df1-ce5d-4ba7-adbe-492ceb80170b", + "size": 1 + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/snapshot_get.json b/labs/openstack-cinder/src/test/resources/snapshot_get.json new file mode 100644 index 0000000000..1c5d558f56 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/snapshot_get.json @@ -0,0 +1,13 @@ +{ + "snapshot": { + "status": "available", + "display_name": "jclouds-test-snapshot", + "created_at": "2012-11-02T16:23:27.000000", + "display_description": "jclouds test snapshot", + "os-extended-snapshot-attributes:progress": "100%", + "volume_id": "ea6f70ef-2784-40b9-9d14-d7f33c507c3f", + "os-extended-snapshot-attributes:project_id": "cc03fd4f503f4d9c986b381b8abe6af5", + "id": "67d03df1-ce5d-4ba7-adbe-492ceb80170b", + "size": 1 + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/snapshot_list_details.json b/labs/openstack-cinder/src/test/resources/snapshot_list_details.json new file mode 100644 index 0000000000..f93736f26d --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/snapshot_list_details.json @@ -0,0 +1,15 @@ +{ + "snapshots": [ + { + "status": "available", + "display_name": "jclouds-test-snapshot", + "created_at": "2012-11-02T16:23:27.000000", + "display_description": "jclouds test snapshot", + "os-extended-snapshot-attributes:progress": "100%", + "volume_id": "ea6f70ef-2784-40b9-9d14-d7f33c507c3f", + "os-extended-snapshot-attributes:project_id": "cc03fd4f503f4d9c986b381b8abe6af5", + "id": "67d03df1-ce5d-4ba7-adbe-492ceb80170b", + "size": 1 + } + ] +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/snapshot_list_simple.json b/labs/openstack-cinder/src/test/resources/snapshot_list_simple.json new file mode 100644 index 0000000000..2ffb8f8949 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/snapshot_list_simple.json @@ -0,0 +1,13 @@ +{ + "snapshots": [ + { + "status": "available", + "display_name": "jclouds-test-snapshot", + "created_at": "2012-11-02T16:23:27.000000", + "display_description": "jclouds test snapshot", + "volume_id": "ea6f70ef-2784-40b9-9d14-d7f33c507c3f", + "id": "67d03df1-ce5d-4ba7-adbe-492ceb80170b", + "size": 1 + } + ] +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_create.json b/labs/openstack-cinder/src/test/resources/volume_create.json new file mode 100644 index 0000000000..6799b041e7 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_create.json @@ -0,0 +1 @@ +{"volume":{"display_name":"jclouds-test-volume","display_description":"description of test volume","size":1}} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_create_response.json b/labs/openstack-cinder/src/test/resources/volume_create_response.json new file mode 100644 index 0000000000..91aaa9df77 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_create_response.json @@ -0,0 +1,15 @@ +{ + "volume": { + "status": "creating", + "display_name": "jclouds-test-volume", + "attachments": [], + "availability_zone": "nova", + "created_at": "2012-10-29T20:53:28.000000", + "display_description": "description of test volume", + "volume_type": "None", + "snapshot_id": null, + "metadata": {}, + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "size": 1 + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_get.json b/labs/openstack-cinder/src/test/resources/volume_get.json new file mode 100644 index 0000000000..c4023177d0 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_get.json @@ -0,0 +1,22 @@ +{ + "volume": { + "status": "in-use", + "display_name": "test", + "attachments": [ + { + "device": "/dev/vdc", + "server_id": "0229a1c1-d54a-4836-8527-2ab28b42e2bb", + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "volume_id": "60761c60-0f56-4499-b522-ff13e120af10" + } + ], + "availability_zone": "nova", + "created_at": "2012-10-29T20:53:28.000000", + "display_description": "This is a test volume", + "volume_type": "None", + "snapshot_id": null, + "metadata": {}, + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "size": 1 + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_list_details.json b/labs/openstack-cinder/src/test/resources/volume_list_details.json new file mode 100644 index 0000000000..e1a8dc8611 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_list_details.json @@ -0,0 +1,24 @@ +{ + "volumes": [ + { + "status": "in-use", + "display_name": "test", + "attachments": [ + { + "device": "/dev/vdc", + "server_id": "0229a1c1-d54a-4836-8527-2ab28b42e2bb", + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "volume_id": "60761c60-0f56-4499-b522-ff13e120af10" + } + ], + "availability_zone": "nova", + "created_at": "2012-10-29T20:53:28.000000", + "display_description": "This is a test volume", + "volume_type": "None", + "snapshot_id": null, + "metadata": {}, + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "size": 1 + } + ] +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_list_simple.json b/labs/openstack-cinder/src/test/resources/volume_list_simple.json new file mode 100644 index 0000000000..e1a8dc8611 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_list_simple.json @@ -0,0 +1,24 @@ +{ + "volumes": [ + { + "status": "in-use", + "display_name": "test", + "attachments": [ + { + "device": "/dev/vdc", + "server_id": "0229a1c1-d54a-4836-8527-2ab28b42e2bb", + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "volume_id": "60761c60-0f56-4499-b522-ff13e120af10" + } + ], + "availability_zone": "nova", + "created_at": "2012-10-29T20:53:28.000000", + "display_description": "This is a test volume", + "volume_type": "None", + "snapshot_id": null, + "metadata": {}, + "id": "60761c60-0f56-4499-b522-ff13e120af10", + "size": 1 + } + ] +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_type_get.json b/labs/openstack-cinder/src/test/resources/volume_type_get.json new file mode 100644 index 0000000000..ffda197216 --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_type_get.json @@ -0,0 +1,14 @@ +{ + "volume_type": { + "name": "jclouds-test-1", + "deleted": false, + "created_at": "2012-05-10 12:33:06", + "updated_at": null, + "extra_specs": { + "test": "value1", + "test1": "wibble" + }, + "deleted_at": null, + "id": 1 + } +} \ No newline at end of file diff --git a/labs/openstack-cinder/src/test/resources/volume_type_list_simple.json b/labs/openstack-cinder/src/test/resources/volume_type_list_simple.json new file mode 100644 index 0000000000..600c380d1c --- /dev/null +++ b/labs/openstack-cinder/src/test/resources/volume_type_list_simple.json @@ -0,0 +1,16 @@ +{ + "volume_types": [ + { + "name": "jclouds-test-1", + "deleted": false, + "created_at": "2012-05-10 12:33:06", + "updated_at": null, + "extra_specs": { + "test": "value1", + "test1": "wibble" + }, + "deleted_at": null, + "id": 1 + } + ] +} \ No newline at end of file diff --git a/labs/pom.xml b/labs/pom.xml index d31b5132a8..5140fc4c7f 100644 --- a/labs/pom.xml +++ b/labs/pom.xml @@ -18,7 +18,8 @@ specific language governing permissions and limitations under the License. ---> +--> + 4.0.0 jclouds-project @@ -61,5 +62,6 @@ fgcp-de abiquo oauth + openstack-cinder