From 1905615c4987bbd77c11bc8a730ed301f12e17c6 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 5 Feb 2012 21:55:39 -0800 Subject: [PATCH] Issue 830: initial vCloud 1.5 with session functionality complete --- core/src/main/resources/rest.properties | 3 + labs/pom.xml | 1 + labs/vcloud-director/pom.xml | 143 ++++++++++++++ .../v1_5/VCloudDirectorAsyncClient.java | 41 ++++ .../director/v1_5/VCloudDirectorClient.java | 44 +++++ .../v1_5/VCloudDirectorContextBuilder.java | 44 +++++ .../v1_5/VCloudDirectorMediaType.java | 37 ++++ .../v1_5/VCloudDirectorPropertiesBuilder.java | 48 +++++ .../director/v1_5/annotations/Login.java | 21 +++ .../director/v1_5/annotations/Session.java | 21 +++ ...AndPasswordAsBasicAuthorizationHeader.java | 63 +++++++ .../VCloudDirectorRestClientModule.java | 171 +++++++++++++++++ .../v1_5/domain/BaseNamedResource.java | 124 +++++++++++++ .../director/v1_5/domain/BaseResource.java | 152 +++++++++++++++ .../vcloud/director/v1_5/domain/Link.java | 148 +++++++++++++++ .../vcloud/director/v1_5/domain/Session.java | 175 ++++++++++++++++++ .../v1_5/domain/SessionWithToken.java | 125 +++++++++++++ .../AddVCloudAuthorizationToRequest.java | 33 ++++ .../functions/LoginUserInOrgWithPassword.java | 58 ++++++ ...eSessionAndRetryOn401AndLogoutOnClose.java | 92 +++++++++ .../handlers/VCloudDirectorErrorHandler.java | 68 +++++++ .../v1_5/login/SessionAsyncClient.java | 78 ++++++++ .../director/v1_5/login/SessionClient.java | 52 ++++++ .../SessionWithTokenFromXMLAndHeader.java | 54 ++++++ .../v1_5/VCloudDirectorClientExpectTest.java | 42 +++++ ...sionAndRetryOn401AndLogoutOnCloseTest.java | 89 +++++++++ .../VCloudDirectorErrorHandlerTest.java | 106 +++++++++++ .../BaseVCloudDirectorClientLiveTest.java | 64 +++++++ ...aseVCloudDirectorRestClientExpectTest.java | 60 ++++++ .../v1_5/login/SessionClientExpectTest.java | 127 +++++++++++++ .../v1_5/login/SessionClientLiveTest.java | 103 +++++++++++ .../src/test/resources/logback.xml | 64 +++++++ .../src/test/resources/session.xml | 7 + 33 files changed, 2458 insertions(+) create mode 100644 labs/vcloud-director/pom.xml create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorAsyncClient.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClient.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorContextBuilder.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorMediaType.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorPropertiesBuilder.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Login.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Session.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/binders/BindUserOrgAndPasswordAsBasicAuthorizationHeader.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/config/VCloudDirectorRestClientModule.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseNamedResource.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseResource.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Link.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Session.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/SessionWithToken.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/filters/AddVCloudAuthorizationToRequest.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/functions/LoginUserInOrgWithPassword.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnClose.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandler.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionAsyncClient.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionClient.java create mode 100644 labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/parsers/SessionWithTokenFromXMLAndHeader.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClientExpectTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnCloseTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandlerTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorClientLiveTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorRestClientExpectTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientExpectTest.java create mode 100644 labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientLiveTest.java create mode 100644 labs/vcloud-director/src/test/resources/logback.xml create mode 100644 labs/vcloud-director/src/test/resources/session.xml diff --git a/core/src/main/resources/rest.properties b/core/src/main/resources/rest.properties index f7aa2439bc..25219b9ca7 100644 --- a/core/src/main/resources/rest.properties +++ b/core/src/main/resources/rest.properties @@ -61,6 +61,9 @@ opscodeplatform.propertiesbuilder=org.jclouds.opscodeplatform.OpscodePlatformPro vcloud.contextbuilder=org.jclouds.vcloud.VCloudContextBuilder vcloud.propertiesbuilder=org.jclouds.vcloud.VCloudPropertiesBuilder +vcloud-director.contextbuilder=org.jclouds.vcloud.director.v1_5.VCloudDirectorContextBuilder +vcloud-director.propertiesbuilder=org.jclouds.vcloud.director.v1_5.VCloudDirectorPropertiesBuilder + eucalyptus.contextbuilder=org.jclouds.ec2.EC2ContextBuilder eucalyptus.propertiesbuilder=org.jclouds.eucalyptus.EucalyptusPropertiesBuilder diff --git a/labs/pom.xml b/labs/pom.xml index dbd3af8047..177d38778b 100644 --- a/labs/pom.xml +++ b/labs/pom.xml @@ -34,5 +34,6 @@ openstack-nova virtualbox + vcloud-director diff --git a/labs/vcloud-director/pom.xml b/labs/vcloud-director/pom.xml new file mode 100644 index 0000000000..b55770a777 --- /dev/null +++ b/labs/vcloud-director/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.5.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + vcloud-director + jcloud vcloud-director api + jclouds components to access an implementation of VMware vCloud Director 1.5+ + bundle + + + https://vcloudbeta.bluelock.com/api + 1.5 + 1.5.0.464915 + FIXME_USERNAME_WHICH_MIGHT_BE_EMAIL@JClouds + FIXME_PASSWORD + + + + + + + + org.jclouds + jclouds-compute + ${project.version} + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds + jclouds-compute + ${project.version} + test-jar + test + + + org.jclouds.driver + jclouds-sshj + ${project.version} + test + + + org.jclouds.driver + jclouds-slf4j + ${project.version} + test + + + ch.qos.logback + logback-classic + 0.9.29 + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.vcloud-director.endpoint} + ${test.vcloud-director.api-version} + ${test.vcloud-director.build-version} + ${test.vcloud-director.identity} + ${test.vcloud-director.credential} + ${test.vcloud-director.image-id} + ${test.vcloud-director.image.login-user} + ${test.vcloud-director.image.authenticate-sudo} + + + + + + + + + + + + + + org.apache.felix + maven-bundle-plugin + + + ${project.artifactId} + org.jclouds.vcloud.director.v1_5.*;version="${project.version}" + + org.jclouds.compute.internal;version="${project.version}", + org.jclouds.rest.internal;version="${project.version}", + org.jclouds.*;version="${project.version}", + * + + + + + + + + diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorAsyncClient.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorAsyncClient.java new file mode 100644 index 0000000000..6ffa4ed3ba --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorAsyncClient.java @@ -0,0 +1,41 @@ +/** + * 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.vcloud.director.v1_5; + +import org.jclouds.vcloud.director.v1_5.domain.Session; + +import com.google.inject.Provides; + + +/** + * Provides asynchronous access to VCloudDirector via their REST API. + *

+ * + * @see VCloudDirectorClient + * @author Adrian Cole + */ +public interface VCloudDirectorAsyncClient { + /** + * + * @return the current login session + */ + @Provides + Session getCurrentSession(); + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClient.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClient.java new file mode 100644 index 0000000000..429d156572 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClient.java @@ -0,0 +1,44 @@ +/** + * 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.vcloud.director.v1_5; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.vcloud.director.v1_5.domain.Session; + +import com.google.inject.Provides; + +/** + * Provides synchronous access to VCloudDirector. + *

+ * + * @see VCloudDirectorAsyncClient + * @author Adrian Cole + */ +@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS) +public interface VCloudDirectorClient { + /** + * + * @return the current login session + */ + @Provides + Session getCurrentSession(); + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorContextBuilder.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorContextBuilder.java new file mode 100644 index 0000000000..bd974278a3 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorContextBuilder.java @@ -0,0 +1,44 @@ +/** + * 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.vcloud.director.v1_5; + +import java.util.List; +import java.util.Properties; + +import org.jclouds.vcloud.director.v1_5.config.VCloudDirectorRestClientModule; +import org.jclouds.rest.RestContextBuilder; + +import com.google.inject.Module; + +/** + * + * @author Adrian Cole + */ +public class VCloudDirectorContextBuilder extends RestContextBuilder { + + public VCloudDirectorContextBuilder(Properties props) { + super(VCloudDirectorClient.class, VCloudDirectorAsyncClient.class, props); + } + + @Override + protected void addClientModule(List modules) { + modules.add(new VCloudDirectorRestClientModule()); + } + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorMediaType.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorMediaType.java new file mode 100644 index 0000000000..88e8632f62 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorMediaType.java @@ -0,0 +1,37 @@ +/** + * 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.vcloud.director.v1_5; + +import javax.ws.rs.core.MediaType; + +/** + * Resource Types used in VCloud + * + *
+ * The object type, specified as a MIME content type, of the object that the link references. This + * attribute is present only for links to objects. It is not present for links to actions. + * + * @see MediaType + */ +public interface VCloudDirectorMediaType { + public final static String NS = "http://www.vmware.com/vcloud/v1.5"; + + public final static String SESSION_XML = "application/vnd.vmware.vcloud.session+xml"; + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorPropertiesBuilder.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorPropertiesBuilder.java new file mode 100644 index 0000000000..bc5fd100ed --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorPropertiesBuilder.java @@ -0,0 +1,48 @@ +/** + * 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.vcloud.director.v1_5; + +import static org.jclouds.Constants.PROPERTY_API_VERSION; +import static org.jclouds.Constants.PROPERTY_ENDPOINT; +import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; + +import java.util.Properties; + +import org.jclouds.PropertiesBuilder; + +/** + * Builds properties used in VCloudDirector Clients + * + * @author Adrian Cole + */ +public class VCloudDirectorPropertiesBuilder extends PropertiesBuilder { + @Override + protected Properties defaultProperties() { + Properties properties = super.defaultProperties(); + properties.setProperty(PROPERTY_ENDPOINT, "http://localhost/api"); + properties.setProperty(PROPERTY_SESSION_INTERVAL, 30*60 + ""); + properties.setProperty(PROPERTY_API_VERSION, "1.5"); + return properties; + } + + public VCloudDirectorPropertiesBuilder(Properties properties) { + super(properties); + } + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Login.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Login.java new file mode 100644 index 0000000000..e777372979 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Login.java @@ -0,0 +1,21 @@ +package org.jclouds.vcloud.director.v1_5.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * The login url for the vCloud, typically {@code https://vdc_host/api/sessions} + * + * @author Adrian Cole + * + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.PARAMETER }) +@Qualifier +public @interface Login { + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Session.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Session.java new file mode 100644 index 0000000000..72ca2d8c1e --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/annotations/Session.java @@ -0,0 +1,21 @@ +package org.jclouds.vcloud.director.v1_5.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * relating to the current session on the vCloud + * + * @author Adrian Cole + * + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.PARAMETER }) +@Qualifier +public @interface Session { + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/binders/BindUserOrgAndPasswordAsBasicAuthorizationHeader.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/binders/BindUserOrgAndPasswordAsBasicAuthorizationHeader.java new file mode 100644 index 0000000000..95674a7222 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/binders/BindUserOrgAndPasswordAsBasicAuthorizationHeader.java @@ -0,0 +1,63 @@ +/** + * 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.vcloud.director.v1_5.binders; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import javax.inject.Singleton; +import javax.ws.rs.core.HttpHeaders; + +import org.jclouds.crypto.CryptoStreams; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.utils.ModifyRequest; +import org.jclouds.rest.MapBinder; + +import com.google.common.base.Throwables; + +/** + * Uses Basic Authentication to sign the request. + * + * @see + * @author Adrian Cole + * + */ +@Singleton +public class BindUserOrgAndPasswordAsBasicAuthorizationHeader implements MapBinder { + + @Override + public R bindToRequest(R request, Map postParams) { + try { + String header = "Basic " + + CryptoStreams.base64(String.format("%s@%s:%s", checkNotNull(postParams.get("user"), "user"), + checkNotNull(postParams.get("org"), "org"), + checkNotNull(postParams.get("password"), "password")).getBytes("UTF-8")); + return ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, header); + } catch (UnsupportedEncodingException e) { + throw Throwables.propagate(e); + } + } + + @Override + public R bindToRequest(R request, Object input) { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/config/VCloudDirectorRestClientModule.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/config/VCloudDirectorRestClientModule.java new file mode 100644 index 0000000000..04d5142f38 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/config/VCloudDirectorRestClientModule.java @@ -0,0 +1,171 @@ +/** + * 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.vcloud.director.v1_5.config; + +import static com.google.common.base.Throwables.propagate; +import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.jclouds.Constants; +import org.jclouds.concurrent.RetryOnTimeOutExceptionFunction; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpRetryHandler; +import org.jclouds.http.RequiresHttp; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; +import org.jclouds.location.Provider; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.rest.config.RestClientModule; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorAsyncClient; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorClient; +import org.jclouds.vcloud.director.v1_5.annotations.Login; +import org.jclouds.vcloud.director.v1_5.domain.Session; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.jclouds.vcloud.director.v1_5.functions.LoginUserInOrgWithPassword; +import org.jclouds.vcloud.director.v1_5.handlers.InvalidateSessionAndRetryOn401AndLogoutOnClose; +import org.jclouds.vcloud.director.v1_5.handlers.VCloudDirectorErrorHandler; +import org.jclouds.vcloud.director.v1_5.login.SessionAsyncClient; +import org.jclouds.vcloud.director.v1_5.login.SessionClient; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; + +/** + * Configures the VCloudDirector connection. + * + * @author Adrian Cole + */ +@RequiresHttp +@ConfiguresRestClient +public class VCloudDirectorRestClientModule extends RestClientModule { + + public static final Map, Class> DELEGATE_MAP = ImmutableMap., Class> builder()// + // TODO + .build(); + + public VCloudDirectorRestClientModule() { + super(VCloudDirectorClient.class, VCloudDirectorAsyncClient.class, DELEGATE_MAP); + } + + @Override + protected void configure() { + // session client is used directly for filters and retry handlers, so let's bind it explicitly + bindClientAndAsyncClient(binder(), SessionClient.class, SessionAsyncClient.class); + bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to( + InvalidateSessionAndRetryOn401AndLogoutOnClose.class); + super.configure(); + } + + @Override + protected void bindErrorHandlers() { + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(VCloudDirectorErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(VCloudDirectorErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(VCloudDirectorErrorHandler.class); + } + + @Provides + @Login + protected Supplier loginUrl(@Provider Supplier provider) { + // TODO: technically, we should implement version client, but this will work + return Suppliers.compose(new Function() { + + @Override + public URI apply(URI arg0) { + return URI.create(arg0.toASCIIString() + "/sessions"); + } + + }, provider); + } + + @Provides + protected Supplier currentSession(Supplier in) { + return Suppliers.compose(new Function() { + + @Override + public Session apply(SessionWithToken arg0) { + return arg0.getSession(); + } + + }, in); + + } + + @Provides + @Singleton + @org.jclouds.vcloud.director.v1_5.annotations.Session + protected Supplier sessionToken(Supplier in) { + return Suppliers.compose(new Function() { + + @Override + public String apply(SessionWithToken arg0) { + return arg0.getToken(); + } + + }, in); + + } + + @Provides + @Singleton + protected Function makeSureFilterRetriesOnTimeout( + LoginUserInOrgWithPassword loginWithPasswordCredentials) { + // we should retry on timeout exception logging in. + return new RetryOnTimeOutExceptionFunction(loginWithPasswordCredentials); + } + + @Provides + @Singleton + public LoadingCache provideSessionWithTokenCache( + Function getSessionWithToken, + @Named(Constants.PROPERTY_SESSION_INTERVAL) int seconds) { + return CacheBuilder.newBuilder().expireAfterWrite(seconds, TimeUnit.SECONDS).build( + CacheLoader.from(getSessionWithToken)); + } + + // Temporary conversion of a cache to a supplier until there is a single-element cache + // http://code.google.com/p/guava-libraries/issues/detail?id=872 + @Provides + @Singleton + protected Supplier provideSessionWithTokenSupplier( + final LoadingCache cache, @Provider final Credentials creds) { + return new Supplier() { + @Override + public SessionWithToken get() { + try { + return cache.get(creds); + } catch (ExecutionException e) { + throw propagate(e.getCause()); + } + } + }; + } +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseNamedResource.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseNamedResource.java new file mode 100644 index 0000000000..7a961f8ec8 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseNamedResource.java @@ -0,0 +1,124 @@ +/** + * 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.vcloud.director.v1_5.domain; + +import static com.google.common.base.Objects.equal; + +import java.net.URI; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAttribute; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * Location of a Rest resource + * + * @author Adrian Cole + * + */ +public class BaseNamedResource> extends BaseResource { + + public static > Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromNamedResource(this); + } + + public static class Builder> extends BaseResource.Builder { + + protected String name; + + /** + * @see BaseNamedResource#getName + */ + public Builder name(String name) { + this.name = name; + return this; + } + + public BaseNamedResource build() { + return new BaseNamedResource(href, type, name); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Builder fromBaseResource(BaseResource in) { + return Builder.class.cast(super.fromBaseResource(in)); + } + + public Builder fromNamedResource(BaseNamedResource in) { + return fromBaseResource(in).name(in.getName()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Builder fromAttributes(Map attributes) { + return Builder.class.cast(super.fromAttributes(attributes)).name(attributes.get("name")); + } + } + + @XmlAttribute + protected String name; + + protected BaseNamedResource(URI href, String type, String name) { + super(href, type); + this.name = name; + } + + protected BaseNamedResource() { + // For JAXB + } + + /** + * The name of the referenced object, taken from the value of that object's name attribute. + * Action links do not include a name attribute. + * + * @return name; + */ + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) + return false; + BaseNamedResource that = BaseNamedResource.class.cast(o); + return equal(name, that.name); + } + + @Override + public int hashCode() { + return super.hashCode() + Objects.hashCode(name); + } + + @Override + public ToStringHelper string() { + return super.string().add("name", name); + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseResource.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseResource.java new file mode 100644 index 0000000000..766df32d17 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/BaseResource.java @@ -0,0 +1,152 @@ +/** + * 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.vcloud.director.v1_5.domain; + +import static com.google.common.base.Objects.equal; + +import java.net.URI; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAttribute; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * Location of a Rest resource + * + * @author Adrian Cole + * + */ +public class BaseResource> { + + public static > Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromBaseResource(this); + } + + public static class Builder> { + + protected String type; + protected URI href; + + /** + * @see BaseResource#getType + */ + public Builder type(String type) { + this.type = type; + return this; + } + + /** + * @see BaseResource#getHref + */ + public Builder href(URI href) { + this.href = href; + return this; + } + + public BaseResource build() { + return new BaseResource(href, type); + } + + protected Builder fromBaseResource(BaseResource in) { + return type(in.getType()).href(in.getHref()); + } + + protected Builder fromAttributes(Map attributes) { + return href(URI.create(attributes.get("href"))).type(attributes.get("type")); + } + + } + + @XmlAttribute + protected String type; + + @XmlAttribute + protected URI href; + + protected BaseResource(URI href, String type) { + this.type = type; + this.href = href; + } + + protected BaseResource() { + // For JAXB + } + + /** + * The object type, specified as a MIME content type, of the object that the link references. + * This attribute is present only for links to objects. It is not present for links to actions. + * + * @return type definition, type, expressed as an HTTP Content-Type + */ + public String getType() { + return type; + } + + /** + * An object reference, expressed in URL format. Because this URL includes the object identifier + * portion of the id attribute value, it uniquely identifies the object, persists for the life of + * the object, and is never reused. The value of the href attribute is a reference to a view of + * the object, and can be used to access a representation of the object that is valid in a + * particular context. Although URLs have a well-known syntax and a well-understood + * interpretation, a client should treat each href as an opaque string. The rules that govern how + * the server constructs href strings might change in future releases. + * + * @return an opaque reference and should never be parsed + */ + public URI getHref() { + return href; + } + + /** + * @see #getHref + */ + public URI getURI() { + return getHref(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BaseResource that = BaseResource.class.cast(o); + return equal(href, that.href) && equal(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hashCode(type, href); + } + + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return Objects.toStringHelper("").add("href", href).add("type", type); + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Link.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Link.java new file mode 100644 index 0000000000..7e6197a845 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Link.java @@ -0,0 +1,148 @@ +package org.jclouds.vcloud.director.v1_5.domain; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URI; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAttribute; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * + * + * @author Adrian Cole + * + */ +public class Link extends BaseNamedResource { + + @SuppressWarnings("unchecked") + public static Builder builder() { + return new Builder(); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder toBuilder() { + return new Builder().fromLink(this); + } + + public static class Builder extends BaseNamedResource.Builder { + + protected String rel; + + /** + * @see Link#getString + */ + public Builder rel(String rel) { + this.rel = rel; + return this; + } + + @Override + public Link build() { + return new Link(href, type, name, rel); + } + + public Builder fromLink(Link in) { + return fromNamedResource(in).rel(in.getRel()); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder fromBaseResource(BaseResource in) { + return Builder.class.cast(super.fromBaseResource(in)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder fromNamedResource(BaseNamedResource in) { + return Builder.class.cast(super.fromNamedResource(in)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder name(String name) { + return Builder.class.cast(super.name(name)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder href(URI href) { + return Builder.class.cast(super.href(href)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder type(String type) { + return Builder.class.cast(super.type(type)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder fromAttributes(Map attributes) { + super.fromAttributes(attributes); + rel(attributes.get("rel")); + return this; + } + } + + @XmlAttribute + protected String rel; + + private Link(URI href, String type, String name, String rel) { + super(href, type, name); + this.rel = checkNotNull(rel, "rel"); + } + + private Link() { + // For JAXB + } + + /** + * Defines the relationship of the link to the object that contains it. A relationship can be the + * name of an operation on the object, a reference to a contained or containing object, or a + * reference to an alternate representation of the object. The relationship value implies the + * HTTP verb to use when you use the link's href value as a request URL. + * + * @return relationship of the link to the object that contains it. + */ + public String getRel() { + return rel; + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) + return false; + Link that = (Link) o; + return equal(this.rel, that.rel); + } + + @Override + public int hashCode() { + return super.hashCode() + Objects.hashCode(rel); + } + + @Override + public ToStringHelper string() { + return super.string().add("rel", rel); + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Session.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Session.java new file mode 100644 index 0000000000..0359f9b9a4 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/Session.java @@ -0,0 +1,175 @@ +/** + * 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.vcloud.director.v1_5.domain; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType.NS; + +import java.net.URI; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +/** + * Login Session + * + * @author Adrian Cole + */ +@XmlRootElement(namespace = NS, name = "Session") +public class Session { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromSession(this); + } + + public static class Builder { + private String user; + private String org; + private URI href; + private Set links = Sets.newLinkedHashSet(); + + /** + * @see Session#getUser + */ + public Builder user(String user) { + this.user = user; + return this; + } + + /** + * @see Session#getOrg + */ + public Builder org(String org) { + this.org = org; + return this; + } + + /** + * @see Session#getHref + */ + public Builder href(URI href) { + this.href = href; + return this; + } + + /** + * @see Session#getLinks + */ + public Builder links(Set links) { + this.links = Sets.newLinkedHashSet(checkNotNull(links, "links")); + return this; + } + + /** + * @see Session#getLinks + */ + public Builder addLink(Link link) { + links.add(checkNotNull(link, "link")); + return this; + } + + public Session build() { + return new Session(user, org, href, links); + } + + public Builder fromSession(Session in) { + return user(in.getUser()).org(in.getOrg()).href(in.getHref()).links(in.getLinks()); + } + } + + private Session() { + // For JAXB and builder use + } + + private Session(String user, String org, URI href, Set links) { + this.user = user; + this.org = org; + this.href = href; + this.links = ImmutableSet.copyOf(links); + } + + @XmlElement(namespace = NS, name = "Link") + private Set links = Sets.newLinkedHashSet(); + @XmlAttribute + private String user; + @XmlAttribute + private String org; + @XmlAttribute + private URI href; + + public Set getLinks() { + return ImmutableSet.copyOf(links); + } + + /** + * + * @return the user's login name. + */ + public String getUser() { + return user; + } + + /** + * + * @return is the name of an organization of which the user is a member + */ + public String getOrg() { + return org; + } + + /** + * + * @return a reference to the current session + */ + public URI getHref() { + return href; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Session that = Session.class.cast(o); + return equal(user, that.user) && equal(org, that.org) && equal(href, that.href) && equal(links, that.links); + } + + @Override + public int hashCode() { + return Objects.hashCode(org, user, href, links); + } + + @Override + public String toString() { + return Objects.toStringHelper("").add("user", user).add("org", org).add("href", href).add("links", links) + .toString(); + } +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/SessionWithToken.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/SessionWithToken.java new file mode 100644 index 0000000000..73fb7df4ee --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/domain/SessionWithToken.java @@ -0,0 +1,125 @@ +/** + * 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.vcloud.director.v1_5.domain; + +import static com.google.common.base.Objects.equal; + +import com.google.common.base.Objects; + +/** + * Session and its corresponding token + * + * @author Adrian Cole + * + */ +public class SessionWithToken { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromSessionWithToken(this); + } + + public static class Builder { + + protected Session session; + protected String token; + + /** + * @see SessionWithToken#getType + */ + public Builder session(Session session) { + this.session = session; + return this; + } + + /** + * @see SessionWithToken#getHref + */ + public Builder token(String token) { + this.token = token; + return this; + } + + public SessionWithToken build() { + return new SessionWithToken(token, session); + } + + protected Builder fromSessionWithToken(SessionWithToken in) { + return session(in.getSession()).token(in.getToken()); + } + + } + + protected Session session; + protected String token; + + protected SessionWithToken(String token, Session session) { + this.session = session; + this.token = token; + } + + protected SessionWithToken() { + // For JAXB + } + + /** + * TODO + */ + public Session getSession() { + return session; + } + + /** + * An object reference, expressed in URL format. Because this URL includes the object identifier + * portion of the id attribute value, it uniquely identifies the object, persists for the life of + * the object, and is never reused. The value of the token attribute is a reference to a view of + * the object, and can be used to access a representation of the object that is valid in a + * particular context. Although URLs have a well-known syntax and a well-understood + * interpretation, a client should treat each token as an opaque string. The rules that govern + * how the server constructs token strings might change in future releases. + * + * @return an opaque reference and should never be parsed + */ + public String getToken() { + return token; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SessionWithToken that = SessionWithToken.class.cast(o); + return equal(token, that.token) && equal(session, that.session); + } + + @Override + public int hashCode() { + return Objects.hashCode(session, token); + } + + @Override + public String toString() { + return Objects.toStringHelper("").add("session", session).add("token", token).toString(); + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/filters/AddVCloudAuthorizationToRequest.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/filters/AddVCloudAuthorizationToRequest.java new file mode 100644 index 0000000000..8aac585958 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/filters/AddVCloudAuthorizationToRequest.java @@ -0,0 +1,33 @@ +package org.jclouds.vcloud.director.v1_5.filters; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.utils.ModifyRequest; +import org.jclouds.vcloud.director.v1_5.annotations.Session; + +import com.google.common.base.Supplier; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class AddVCloudAuthorizationToRequest implements HttpRequestFilter { + + private final Supplier sessionSupplier; + + @Inject + public AddVCloudAuthorizationToRequest(@Session Supplier sessionSupplier) { + this.sessionSupplier = sessionSupplier; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + return ModifyRequest.replaceHeader(request, "x-vcloud-authorization", sessionSupplier.get()); + } + +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/functions/LoginUserInOrgWithPassword.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/functions/LoginUserInOrgWithPassword.java new file mode 100644 index 0000000000..0e9261a6b5 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/functions/LoginUserInOrgWithPassword.java @@ -0,0 +1,58 @@ +/** + * 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.vcloud.director.v1_5.functions; + +import java.net.URI; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.domain.Credentials; +import org.jclouds.vcloud.director.v1_5.annotations.Login; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.jclouds.vcloud.director.v1_5.login.SessionClient; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; + +@Singleton +public class LoginUserInOrgWithPassword implements Function { + private final SessionClient client; + private final Supplier loginUrl; + + @Inject + public LoginUserInOrgWithPassword(SessionClient client, @Login Supplier loginUrl) { + this.client = client; + this.loginUrl = loginUrl; + } + + @Override + public SessionWithToken apply(Credentials input) { + String user = input.identity.substring(0, input.identity.lastIndexOf('@')); + String org = input.identity.substring(input.identity.lastIndexOf('@') + 1); + String password = input.credential; + + return client.loginUserInOrgWithPassword(loginUrl.get(), user, org, password); + } + + @Override + public String toString() { + return "loginUserInOrgWithPassword()"; + } +} \ No newline at end of file diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnClose.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnClose.java new file mode 100644 index 0000000000..72ab0eba91 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnClose.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.vcloud.director.v1_5.handlers; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; +import static org.jclouds.http.HttpUtils.releasePayload; + +import javax.annotation.PreDestroy; +import javax.annotation.Resource; + +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.handlers.BackoffLimitedRetryHandler; +import org.jclouds.logging.Logger; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.jclouds.vcloud.director.v1_5.login.SessionClient; + +import com.google.common.cache.LoadingCache; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * If the credentials supplied in the authentication header are invalid, or if the token has + * expired, the server returns HTTP response code 401. The token expires after a configurable + * interval of client inactivity. The default is 30 minutes after the token is created. After the + * token expires, you must log in again to obtain a new token. + * + * @author Adrian Cole + * + */ +@Singleton +public class InvalidateSessionAndRetryOn401AndLogoutOnClose extends BackoffLimitedRetryHandler { + @Resource + protected Logger logger = Logger.NULL; + + private final LoadingCache authenticationResponseCache; + private final SessionClient sessionClient; + + @Inject + protected InvalidateSessionAndRetryOn401AndLogoutOnClose( + LoadingCache authenticationResponseCache, SessionClient sessionClient) { + this.authenticationResponseCache = authenticationResponseCache; + this.sessionClient = sessionClient; + } + + @Override + public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) { + boolean retry = false; // default + try { + if (response.getStatusCode() == 401) { + closeClientButKeepContentStream(response); + logger.debug("invalidating session"); + authenticationResponseCache.invalidateAll(); + retry = super.shouldRetryRequest(command, response); + } + return retry; + } finally { + releasePayload(response); + } + } + + /** + * it is important that we close any sessions on close to help the server not become overloaded. + */ + @PreDestroy + public void logoutOnClose() { + for (SessionWithToken s : authenticationResponseCache.asMap().values()) { + try { + sessionClient.logoutSessionWithToken(s.getSession().getHref(), s.getToken()); + } catch (Exception e) { + logger.error(e, "error logging out session %s", s.getSession()); + } + } + } +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandler.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandler.java new file mode 100644 index 0000000000..6b54f730c6 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandler.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.vcloud.director.v1_5.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.ResourceNotFoundException; + +/** + * This will parse and set an appropriate exception on the command object. + * + * @author Adrian Cole + * + */ +//TODO: is there error spec someplace? let's type errors, etc. +@Singleton +public class VCloudDirectorErrorHandler 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 401: + case 403: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + default: + exception = new HttpResponseException(command, response, message); + break; + } + command.setException(exception); + } + +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionAsyncClient.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionAsyncClient.java new file mode 100644 index 0000000000..c69a3caa84 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionAsyncClient.java @@ -0,0 +1,78 @@ +/** + * 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.vcloud.director.v1_5.login; + +import java.net.URI; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; + +import org.jclouds.rest.annotations.EndpointParam; +import org.jclouds.rest.annotations.JAXBResponseParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.vcloud.director.v1_5.binders.BindUserOrgAndPasswordAsBasicAuthorizationHeader; +import org.jclouds.vcloud.director.v1_5.domain.Session; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.jclouds.vcloud.director.v1_5.parsers.SessionWithTokenFromXMLAndHeader; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to Session via their REST API. + *

+ * + * @see SessionClient + * @author Adrian Cole + */ +public interface SessionAsyncClient { + + /** + * @see SessionClient#loginUserInOrgWithPassword + */ + @POST + @Consumes + @ResponseParser(SessionWithTokenFromXMLAndHeader.class) + @MapBinder(BindUserOrgAndPasswordAsBasicAuthorizationHeader.class) + ListenableFuture loginUserInOrgWithPassword(@EndpointParam URI loginUrl, + @PayloadParam("user") String user, @PayloadParam("org") String org, + @PayloadParam("password") String password); + + /** + * @see SessionClient#getSessionWithToken + */ + @GET + @Consumes + @JAXBResponseParser + ListenableFuture getSessionWithToken(@EndpointParam URI session, + @HeaderParam("x-vcloud-authorization") String authenticationToken); + + /** + * @see SessionClient#logoutSessionWithToken + */ + @DELETE + @Consumes + @JAXBResponseParser + ListenableFuture logoutSessionWithToken(@EndpointParam URI session, + @HeaderParam("x-vcloud-authorization") String authenticationToken); +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionClient.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionClient.java new file mode 100644 index 0000000000..02a0978012 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/login/SessionClient.java @@ -0,0 +1,52 @@ +/** + * 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.vcloud.director.v1_5.login; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.vcloud.director.v1_5.domain.Session; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; + +/** + * Provides synchronous access to Session. + *

+ * + * @see SessionAsyncClient + * @author Adrian Cole + */ +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface SessionClient { + + /** + * TODO + */ + SessionWithToken loginUserInOrgWithPassword(URI loginUrl, String user, String org, String password); + + /** + * TODO + */ + Session getSessionWithToken(URI session, String authenticationToken); + + /** + * TODO + */ + void logoutSessionWithToken(URI session, String authenticationToken); +} diff --git a/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/parsers/SessionWithTokenFromXMLAndHeader.java b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/parsers/SessionWithTokenFromXMLAndHeader.java new file mode 100644 index 0000000000..e9c6a885c7 --- /dev/null +++ b/labs/vcloud-director/src/main/java/org/jclouds/vcloud/director/v1_5/parsers/SessionWithTokenFromXMLAndHeader.java @@ -0,0 +1,54 @@ +/** + * 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.vcloud.director.v1_5.parsers; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseXMLWithJAXB; +import org.jclouds.logging.Logger; +import org.jclouds.vcloud.director.v1_5.domain.Session; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; + +import com.google.common.base.Function; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class SessionWithTokenFromXMLAndHeader implements Function { + @Resource + protected Logger logger = Logger.NULL; + private ParseXMLWithJAXB sessionParser; + + @Inject + public SessionWithTokenFromXMLAndHeader(ParseXMLWithJAXB sessionParser) { + this.sessionParser = sessionParser; + } + + @Override + public SessionWithToken apply(final HttpResponse from) { + Session session = sessionParser.apply(from); + return SessionWithToken.builder().session(session).token(from.getFirstHeaderOrNull("x-vcloud-authorization")) + .build(); + } +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClientExpectTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClientExpectTest.java new file mode 100644 index 0000000000..2806e74726 --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/VCloudDirectorClientExpectTest.java @@ -0,0 +1,42 @@ +/** + * 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.vcloud.director.v1_5; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.vcloud.director.v1_5.internal.BaseVCloudDirectorRestClientExpectTest; +import org.jclouds.vcloud.director.v1_5.login.SessionClientExpectTest; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "VCloudDirectorClient") +public class VCloudDirectorClientExpectTest extends BaseVCloudDirectorRestClientExpectTest { + + public void testRestClientModuleWorksProperly() throws Exception { + + VCloudDirectorClient clientWhenSessionsExist = requestSendsResponse(loginRequest, sessionResponse); + + assertEquals(clientWhenSessionsExist.getCurrentSession(), SessionClientExpectTest.SESSION); + + } + +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnCloseTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnCloseTest.java new file mode 100644 index 0000000000..ec0f1dc55e --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/InvalidateSessionAndRetryOn401AndLogoutOnCloseTest.java @@ -0,0 +1,89 @@ +/** + * 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.vcloud.director.v1_5.handlers; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.jclouds.vcloud.director.v1_5.login.SessionClient; +import org.testng.annotations.Test; + +import com.google.common.cache.LoadingCache; + +/** + * Tests behavior of {@code InvalidateSessionAndRetryOn401AndLogoutOnClose} handler + * + * @author grkvlt@apache.org + */ +@Test(groups = "unit", testName = "InvalidateSessionAndRetryOn401AndLogoutOnCloseTest") +public class InvalidateSessionAndRetryOn401AndLogoutOnCloseTest { + @SuppressWarnings("unchecked") + @Test + public void test401ShouldInvalidateSessionAndRetry() { + HttpCommand command = createMock(HttpCommand.class); + SessionClient sessionClient = createMock(SessionClient.class); + LoadingCache cache = createMock(LoadingCache.class); + + cache.invalidateAll(); + expectLastCall(); + expect(command.incrementFailureCount()).andReturn(1); + expect(command.isReplayable()).andReturn(true); + expect(command.getFailureCount()).andReturn(1).atLeastOnce(); + + replay(cache, command); + + HttpResponse response = HttpResponse.builder().statusCode(401).build(); + + InvalidateSessionAndRetryOn401AndLogoutOnClose retry = new InvalidateSessionAndRetryOn401AndLogoutOnClose(cache, + sessionClient); + + assertTrue(retry.shouldRetryRequest(command, response)); + + verify(cache, command); + } + + @SuppressWarnings("unchecked") + @Test + public void test403ShouldNotInvalidateSessionOrRetry() { + HttpCommand command = createMock(HttpCommand.class); + SessionClient sessionClient = createMock(SessionClient.class); + LoadingCache cache = createMock(LoadingCache.class); + + replay(cache, command); + + HttpResponse response = HttpResponse.builder().statusCode(403).build(); + + InvalidateSessionAndRetryOn401AndLogoutOnClose retry = new InvalidateSessionAndRetryOn401AndLogoutOnClose(cache, + sessionClient); + + assertFalse(retry.shouldRetryRequest(command, response)); + + verify(cache, command); + } + +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandlerTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandlerTest.java new file mode 100644 index 0000000000..4d06290fd0 --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/handlers/VCloudDirectorErrorHandlerTest.java @@ -0,0 +1,106 @@ +/** + * 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.vcloud.director.v1_5.handlers; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.verify; + +import java.net.URI; + +import org.easymock.IArgumentMatcher; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.io.Payloads; +import org.jclouds.vcloud.director.v1_5.handlers.VCloudDirectorErrorHandler; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import com.google.inject.Guice; + +/** + * + * @author Adrian Cole + */ +@Test(groups = { "unit" }) +public class VCloudDirectorErrorHandlerTest { + + @Test + public void test401MakesAuthorizationException() { + assertCodeMakes("GET", URI.create("https://api.vcloud.director.com/foo"), 401, "", "Unauthorized", + AuthorizationException.class); + } + + @Test + public void test404MakesResourceNotFoundException() { + assertCodeMakes("GET", URI.create("https://api.vcloud.director.com/foo"), 404, "", "Not Found", + ResourceNotFoundException.class); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content, + Class expected) { + assertCodeMakes(method, uri, statusCode, message, "text/xml", content, expected); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType, + String content, Class expected) { + + VCloudDirectorErrorHandler function = Guice.createInjector().getInstance(VCloudDirectorErrorHandler.class); + + HttpCommand command = createMock(HttpCommand.class); + HttpRequest request = new HttpRequest(method, uri); + HttpResponse response = new HttpResponse(statusCode, message, Payloads.newInputStreamPayload(Strings2 + .toInputStream(content))); + response.getPayload().getContentMetadata().setContentType(contentType); + + expect(command.getCurrentRequest()).andReturn(request).atLeastOnce(); + command.setException(classEq(expected)); + + replay(command); + + function.handleError(command, response); + + verify(command); + } + + public static Exception classEq(final Class in) { + reportMatcher(new IArgumentMatcher() { + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("classEq("); + buffer.append(in); + buffer.append(")"); + } + + @Override + public boolean matches(Object arg) { + return arg.getClass() == in; + } + + }); + return null; + } + +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorClientLiveTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorClientLiveTest.java new file mode 100644 index 0000000000..74662b0bdd --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorClientLiveTest.java @@ -0,0 +1,64 @@ +/** + * 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.vcloud.director.v1_5.internal; + +import java.util.Properties; + +import org.jclouds.compute.BaseVersionedServiceLiveTest; +import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorAsyncClient; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorClient; +import org.jclouds.rest.RestContext; +import org.jclouds.rest.RestContextFactory; +import org.jclouds.sshj.config.SshjSshClientModule; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; + +/** + * Tests behavior of {@code VCloudDirectorClient} + * + * @author Adrian Cole + */ +@Test(groups = "live") +public class BaseVCloudDirectorClientLiveTest extends BaseVersionedServiceLiveTest { + public BaseVCloudDirectorClientLiveTest() { + provider = "vcloud-director"; + } + + protected RestContext context; + + @BeforeGroups(groups = { "live" }) + public void setupClient() { + setupCredentials(); + Properties overrides = setupProperties(); + context = new RestContextFactory().createContext(provider, identity, credential, + ImmutableSet. of(new SLF4JLoggingModule(), new SshjSshClientModule()), overrides); + } + + @AfterGroups(groups = "live") + protected void tearDown() { + if (context != null) + context.close(); + } + +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorRestClientExpectTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorRestClientExpectTest.java new file mode 100644 index 0000000000..99f5eea22b --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/internal/BaseVCloudDirectorRestClientExpectTest.java @@ -0,0 +1,60 @@ +/** + * 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.vcloud.director.v1_5.internal; + +import java.net.URI; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.rest.BaseRestClientExpectTest; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorClient; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType; + +import com.google.common.collect.ImmutableMultimap; + +/** + * Base class for writing KeyStone Rest Client Expect tests + * + * @author Adrian Cole + */ +public class BaseVCloudDirectorRestClientExpectTest extends BaseRestClientExpectTest { + + public static final String user = "adrian@jclouds.org"; + public static final String org = "JClouds"; + public static final String password = "password"; + public static final String token = "mIaR3/6Lna8DWImd7/JPR5rK8FcUHabt+G/UCJV5pJQ="; + + protected HttpRequest loginRequest = HttpRequest.builder().method("POST").endpoint( + URI.create("http://localhost/api/sessions")).headers( + ImmutableMultimap. builder().put("Accept", "*/*").put("Authorization", + "Basic YWRyaWFuQGpjbG91ZHMub3JnQEpDbG91ZHM6cGFzc3dvcmQ=").build()).build(); + + protected HttpResponse sessionResponse = HttpResponse.builder().statusCode(200).headers( + ImmutableMultimap. builder().put("x-vcloud-authorization", token).put("Set-Cookie", + String.format("vcloud-token=%s; Secure; Path=/", token)).build()).payload( + payloadFromResourceWithContentType("/session.xml", VCloudDirectorMediaType.SESSION_XML + ";version=1.5")) + .build(); + + public BaseVCloudDirectorRestClientExpectTest() { + provider = "vcloud-director"; + identity = String.format("%s@%s", user, org); + credential = password; + } + +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientExpectTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientExpectTest.java new file mode 100644 index 0000000000..879db3a1ef --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientExpectTest.java @@ -0,0 +1,127 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + *(Link.builder().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(Link.builder().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.vcloud.director.v1_5.login; + +import static org.testng.Assert.assertEquals; + +import java.net.URI; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.rest.BaseRestClientExpectTest; +import org.jclouds.rest.BaseRestClientExpectTest.RegisterContext; +import org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType; +import org.jclouds.vcloud.director.v1_5.domain.Link; +import org.jclouds.vcloud.director.v1_5.domain.Session; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMultimap; + +/** + * + * Allows us to test a client via its side effects. + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "SessionClientExpectTest") +// only needed as SessionClient is not(Link.builder().registered in(Link.builder().rest.properties +@RegisterContext(sync = SessionClient.class, async = SessionAsyncClient.class) +public class SessionClientExpectTest extends BaseRestClientExpectTest { + public static final String user = "adrian@jclouds.org"; + public static final String org = "JClouds"; + public static final Session SESSION = Session.builder().user(user).org(org).href( + URI.create("https://vcloudbeta.bluelock.com/api/session/")).addLink( + Link.builder().rel("down").type("application/vnd.vmware.vcloud.orgList+xml").href( + URI.create("https://vcloudbeta.bluelock.com/api/org/")).build()).addLink( + Link.builder().rel("down").type("application/vnd.vmware.admin.vcloud+xml").href( + URI.create("https://vcloudbeta.bluelock.com/api/admin/")).build()).addLink( + Link.builder().rel("down").type("application/vnd.vmware.vcloud.query.queryList+xml").href( + URI.create("https://vcloudbeta.bluelock.com/api/query")).build()).addLink( + Link.builder().rel("entityResolver").type("application/vnd.vmware.vcloud.entity+xml").href( + URI.create("https://vcloudbeta.bluelock.com/api/entity/")).build()).build(); + + public void testWhenResponseIs2xxLoginReturnsValidSession() { + URI loginUrl = URI.create("https://vcloudbeta.bluelock.com/api/sessions"); + + String token = "mIaR3/6Lna8DWImd7/JPR5rK8FcUHabt+G/UCJV5pJQ="; + + SessionClient client = requestSendsResponse( + + HttpRequest.builder().method("POST").endpoint(loginUrl).headers( + ImmutableMultimap. builder().put("Accept", "*/*").put("Authorization", + "Basic YWRyaWFuQGpjbG91ZHMub3JnQEpDbG91ZHM6cGFzc3dvcmQ=").build()).build(), + + HttpResponse.builder().statusCode(200).headers( + ImmutableMultimap. builder().put("x-vcloud-authorization", token).put("Set-Cookie", + String.format("vcloud-token=%s; Secure; Path=/", token)).build()) + .payload( + payloadFromResourceWithContentType("/session.xml", VCloudDirectorMediaType.SESSION_XML + + ";version=1.5")).build() + + ); + + assertEquals(client.loginUserInOrgWithPassword(loginUrl, user, org, "password"), SessionWithToken.builder() + .session(SESSION) + + .token(token).build()); + + } + + public void testWhenResponseIs2xxGetSessionReturnsValidSession() { + URI sessionUrl = URI.create("https://vcloudbeta.bluelock.com/api/session"); + + String token = "mIaR3/6Lna8DWImd7/JPR5rK8FcUHabt+G/UCJV5pJQ="; + + SessionClient client = requestSendsResponse( + + HttpRequest.builder().method("GET").endpoint(sessionUrl).headers( + ImmutableMultimap. builder().put("x-vcloud-authorization", token).put("Accept", "*/*") + .build()).build(), + + HttpResponse.builder().statusCode(200) + .payload( + payloadFromResourceWithContentType("/session.xml", VCloudDirectorMediaType.SESSION_XML + + ";version=1.5")).build() + + ); + + assertEquals(client.getSessionWithToken(sessionUrl, token), SESSION); + + } + + public void testLogoutWhenResponseIs2xx() { + URI sessionUrl = URI.create("https://vcloudbeta.bluelock.com/api/session"); + + String token = "mIaR3/6Lna8DWImd7/JPR5rK8FcUHabt+G/UCJV5pJQ="; + + SessionClient client = requestSendsResponse( + + HttpRequest.builder().method("DELETE").endpoint(sessionUrl).headers( + ImmutableMultimap. builder().put("x-vcloud-authorization", token).put("Accept", "*/*") + .build()).build(), + + HttpResponse.builder().statusCode(204).build() + + ); + + client.logoutSessionWithToken(sessionUrl, token); + + } +} diff --git a/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientLiveTest.java b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientLiveTest.java new file mode 100644 index 0000000000..040a34f97a --- /dev/null +++ b/labs/vcloud-director/src/test/java/org/jclouds/vcloud/director/v1_5/login/SessionClientLiveTest.java @@ -0,0 +1,103 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + *(Link.builder().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(Link.builder().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.vcloud.director.v1_5.login; + +import static org.jclouds.rest.RestContextFactory.contextSpec; +import static org.jclouds.rest.RestContextFactory.createContextBuilder; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.compute.BaseVersionedServiceLiveTest; +import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; +import org.jclouds.rest.RestContext; +import org.jclouds.rest.RestContextSpec; +import org.jclouds.vcloud.director.v1_5.domain.SessionWithToken; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; + +/** + * Tests behavior of {@code SessionClient}. Note this class is tested completely independently of + * VCloudClient as it is a dependency of the VCloud context working. + * + * @author Adrian Cole + */ +@Test(groups = { "live", "apitests" }, testName = "SessionClientLiveTest") +public class SessionClientLiveTest extends BaseVersionedServiceLiveTest { + public SessionClientLiveTest() { + provider = "vcloud-director"; + } + + private RestContext context; + + @BeforeGroups(groups = { "live" }) + public void setupClient() { + setupCredentials(); + Properties overrides = setupProperties(); + RestContextSpec contextSpec = contextSpec("vcloud-director", endpoint, + apiVersion, buildVersion, "", identity, credential, SessionClient.class, SessionAsyncClient.class); + + context = createContextBuilder(contextSpec, overrides).withModules( + ImmutableSet. of(new SLF4JLoggingModule())).buildContext(); + + // session client isn't typically exposed to the user, as it is implicit + client = context.utils().injector().getInstance(SessionClient.class); + } + + private SessionClient client; + private SessionWithToken sessionWithToken; + + @Test(testName = "POST /sessions") + public void testLogin() { + String user = identity.substring(0, identity.lastIndexOf('@')); + String org = identity.substring(identity.lastIndexOf('@') + 1); + String password = credential; + + sessionWithToken = client.loginUserInOrgWithPassword(URI.create(endpoint + "/sessions"), user, org, password); + assertEquals(sessionWithToken.getSession().getUser(), user); + assertEquals(sessionWithToken.getSession().getOrg(), org); + assertTrue(sessionWithToken.getSession().getLinks().size() > 0); + assertNotNull(sessionWithToken.getToken()); + } + + @Test(testName = "GET /session", dependsOnMethods = "testLogin") + public void testGetSession() { + assertEquals(client.getSessionWithToken(sessionWithToken.getSession().getHref(), sessionWithToken.getToken()), + sessionWithToken.getSession()); + } + + @Test(testName = "DELETE /session", dependsOnMethods = "testGetSession") + public void testLogout() { + client.logoutSessionWithToken(sessionWithToken.getSession().getHref(), sessionWithToken.getToken()); + } + + @AfterGroups(groups = "live") + protected void tearDown() { + if (context != null) + context.close(); + } + +} diff --git a/labs/vcloud-director/src/test/resources/logback.xml b/labs/vcloud-director/src/test/resources/logback.xml new file mode 100644 index 0000000000..c1bb1b78c4 --- /dev/null +++ b/labs/vcloud-director/src/test/resources/logback.xml @@ -0,0 +1,64 @@ + + + + target/test-data/jclouds.log + + + %d %-5p [%c] (%t) %m%n + + + + + target/test-data/jclouds-wire.log + + + %d %-5p [%c] (%t) %m%n + + + + + target/test-data/jclouds-compute.log + + + %d %-5p [%c] (%t) %m%n + + + + + target/test-data/jclouds-ssh.log + + + %d %-5p [%c] (%t) %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/labs/vcloud-director/src/test/resources/session.xml b/labs/vcloud-director/src/test/resources/session.xml new file mode 100644 index 0000000000..ec24f12447 --- /dev/null +++ b/labs/vcloud-director/src/test/resources/session.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file