Issue 299: converted 409 -> IllegalStateException and mapped AWS and vCloud exceptions

This commit is contained in:
Adrian Cole 2010-07-02 23:19:11 -07:00
parent 85b5bd48f1
commit f29d003914
25 changed files with 786 additions and 273 deletions

View File

@ -64,8 +64,8 @@ public class AWSClientErrorRetryHandler implements HttpRetryHandler {
// Content can be null in the case of HEAD requests
if (content != null) {
try {
AWSError error = utils.parseAWSErrorFromContent(command, response, new String(
content));
AWSError error = utils.parseAWSErrorFromContent(command.getRequest(), response,
new String(content));
if ("RequestTimeout".equals(error.getCode())
|| "OperationAborted".equals(error.getCode())
|| "SignatureDoesNotMatch".equals(error.getCode())) {

View File

@ -64,8 +64,8 @@ public class AWSRedirectionRetryHandler extends RedirectionRetryHandler {
} else {
command.incrementRedirectCount();
try {
AWSError error = utils.parseAWSErrorFromContent(command, response, new String(
content));
AWSError error = utils.parseAWSErrorFromContent(command.getRequest(), response,
new String(content));
String host = error.getDetails().get("Endpoint");
if (host != null) {
if (host.equals(command.getRequest().getEndpoint().getHost())) {

View File

@ -31,6 +31,7 @@ import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.KeyNotFoundException;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.logging.Logger;
@ -38,6 +39,7 @@ import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.jclouds.util.Utils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Closeables;
/**
@ -52,37 +54,41 @@ public class ParseAWSErrorFromXmlContent implements HttpErrorHandler {
@Resource
protected Logger logger = Logger.NULL;
private final AWSUtils utils;
@VisibleForTesting
final AWSUtils utils;
@Inject
public ParseAWSErrorFromXmlContent(AWSUtils utils) {
ParseAWSErrorFromXmlContent(AWSUtils utils) {
this.utils = utils;
}
public void handleError(HttpCommand command, HttpResponse response) {
HttpRequest request = command.getRequest();
Exception exception = new HttpResponseException(command, response);
try {
AWSError error = parseErrorFromContentOrNull(command, response);
exception = error != null ? new AWSResponseException(command,
response, error) : exception;
AWSError error = parseErrorFromContentOrNull(request, response);
exception = error != null ? new AWSResponseException(command, response, error) : exception;
switch (response.getStatusCode()) {
case 400:
if (error.getCode().endsWith(".NotFound"))
exception = new ResourceNotFoundException(error.getMessage(),
exception);
break;
case 401:
case 403:
exception = new ResourceNotFoundException(error.getMessage(), exception);
else if (error.getCode().equals("IncorrectState"))
exception = new IllegalStateException(error.getMessage(), exception);
else if (error.getCode().equals("AuthFailure"))
exception = new AuthorizationException(command.getRequest(),
error != null ? error.getMessage() : response.getStatusLine());
break;
case 401:
case 403:
exception = new AuthorizationException(command.getRequest(), error != null ? error
.getMessage() : response.getStatusLine());
break;
case 404:
if (!command.getRequest().getMethod().equals("DELETE")) {
String message = error != null ? error.getMessage() : String
.format("%s -> %s", command.getRequest().getRequestLine(),
response.getStatusLine());
String container = command.getRequest().getEndpoint().getHost();
String key = command.getRequest().getEndpoint().getPath();
String message = error != null ? error.getMessage() : String.format("%s -> %s",
request.getRequestLine(), response.getStatusLine());
String container = request.getEndpoint().getHost();
String key = request.getEndpoint().getPath();
if (key == null || key.equals("/"))
exception = new ContainerNotFoundException(container, message);
else
@ -96,14 +102,12 @@ public class ParseAWSErrorFromXmlContent implements HttpErrorHandler {
}
}
AWSError parseErrorFromContentOrNull(HttpCommand command,
HttpResponse response) {
AWSError parseErrorFromContentOrNull(HttpRequest request, HttpResponse response) {
if (response.getContent() != null) {
try {
String content = Utils.toStringAndClose(response.getContent());
if (content != null && content.indexOf('<') >= 0)
return utils
.parseAWSErrorFromContent(command, response, content);
return utils.parseAWSErrorFromContent(request, response, content);
} catch (IOException e) {
logger.warn(e, "exception reading error from response", response);
}

View File

@ -50,7 +50,7 @@ public class S3Utils {
public AWSError parseAWSErrorFromContent(HttpCommand command, HttpResponse response,
InputStream content) throws HttpException {
AWSError error = util.parseAWSErrorFromContent(command, response, content);
AWSError error = util.parseAWSErrorFromContent(command.getRequest(), response, content);
if (error.getRequestId() == null)
error.setRequestId(response.getFirstHeaderOrNull(S3Headers.REQUEST_ID));
error.setRequestToken(response.getFirstHeaderOrNull(S3Headers.REQUEST_TOKEN));

View File

@ -23,12 +23,14 @@ import java.io.InputStream;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.jclouds.aws.domain.AWSError;
import org.jclouds.aws.xml.ErrorHandler;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.Factory;
import org.jclouds.rest.RequestSigner;
/**
@ -36,30 +38,32 @@ import org.jclouds.rest.RequestSigner;
*
* @author Adrian Cole
*/
@Singleton
public class AWSUtils {
private final RequestSigner signer;
private final ParseSax.Factory factory;
private final Provider<ErrorHandler> errorHandlerProvider;
@Inject
RequestSigner signer;
AWSUtils(RequestSigner signer, Factory factory, Provider<ErrorHandler> errorHandlerProvider) {
this.signer = signer;
this.factory = factory;
this.errorHandlerProvider = errorHandlerProvider;
}
@Inject
ParseSax.Factory factory;
@Inject
Provider<ErrorHandler> errorHandlerProvider;
public AWSError parseAWSErrorFromContent(HttpCommand command, HttpResponse response,
public AWSError parseAWSErrorFromContent(HttpRequest request, HttpResponse response,
InputStream content) {
AWSError error = (AWSError) factory.create(errorHandlerProvider.get()).parse(content);
if ("SignatureDoesNotMatch".equals(error.getCode())) {
error.setStringSigned(signer.createStringToSign(command.getRequest()));
error.setStringSigned(signer.createStringToSign(request));
error.setSignature(signer.sign(error.getStringSigned()));
}
return error;
}
public AWSError parseAWSErrorFromContent(HttpCommand command, HttpResponse response,
public AWSError parseAWSErrorFromContent(HttpRequest request, HttpResponse response,
String content) {
return parseAWSErrorFromContent(command, response, new ByteArrayInputStream(content
return parseAWSErrorFromContent(request, response, new ByteArrayInputStream(content
.getBytes()));
}
}

View File

@ -18,6 +18,8 @@
*/
package org.jclouds.aws.xml;
import javax.inject.Singleton;
import org.jclouds.aws.domain.AWSError;
import org.jclouds.http.functions.ParseSax;
@ -29,6 +31,7 @@ import org.jclouds.http.functions.ParseSax;
* />
* @author Adrian Cole
*/
@Singleton
public class ErrorHandler extends ParseSax.HandlerWithResult<AWSError> {
private AWSError error = new AWSError();

View File

@ -0,0 +1,99 @@
package org.jclouds.aws.handlers;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.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.http.functions.config.ParserModule;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.ResourceNotFoundException;
import org.jclouds.util.Utils;
import org.testng.annotations.Test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public class ParseAWSErrorFromXmlContentTest {
@Test
public void test400WithNotFoundSetsResourceNotFoundException() {
assertCodeMakes("GET", URI.create("https://amazonaws.com/foo"), 400, "",
"<Error><Code>Monster.NotFound</Code></Error>", ResourceNotFoundException.class);
}
@Test
public void test400WithIncorrectStateSetsIllegalStateException() {
assertCodeMakes("GET", URI.create("https://amazonaws.com/foo"), 400, "",
"<Error><Code>IncorrectState</Code></Error>", IllegalStateException.class);
}
@Test
public void test400WithAuthFailureSetsAuthorizationException() {
assertCodeMakes("GET", URI.create("https://amazonaws.com/foo"), 400, "",
"<Error><Code>AuthFailure</Code></Error>", AuthorizationException.class);
}
private void assertCodeMakes(String method, URI uri, int statusCode, String message,
String content, Class<? extends Exception> expected) {
ParseAWSErrorFromXmlContent function = Guice.createInjector(new ParserModule(),
new AbstractModule() {
@Override
protected void configure() {
bind(RequestSigner.class).toInstance(createMock(RequestSigner.class));
}
}).getInstance(ParseAWSErrorFromXmlContent.class);
HttpCommand command = createMock(HttpCommand.class);
HttpRequest request = new HttpRequest(method, uri);
HttpResponse response = new HttpResponse(Utils.toInputStream(content));
response.setStatusCode(statusCode);
response.setMessage(message);
expect(command.getRequest()).andReturn(request).atLeastOnce();
command.setException(classEq(expected));
replay(command);
function.handleError(command, response);
verify(command);
}
public static Exception classEq(final Class<? extends Exception> 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;
}
}

View File

@ -18,7 +18,9 @@
*/
package org.jclouds.aws.s3.util;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
@ -29,6 +31,7 @@ import org.jclouds.aws.domain.AWSError;
import org.jclouds.aws.s3.reference.S3Headers;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.logging.config.NullLoggingModule;
import org.jclouds.rest.RestContextFactory;
@ -46,7 +49,7 @@ import com.google.inject.Injector;
*
* @author Adrian Cole
*/
@Test(groups = { "unit" }, testName = "s3.S3UtilsTest")
@Test(sequential = true, groups = { "unit" }, testName = "s3.S3UtilsTest")
public class S3UtilsTest {
S3Utils utils = null;
private HttpResponse response;
@ -65,7 +68,8 @@ public class S3UtilsTest {
response.getHeaders().put(S3Headers.REQUEST_ID, "requestid");
response.getHeaders().put(S3Headers.REQUEST_TOKEN, "requesttoken");
command = createMock(HttpCommand.class);
expect(command.getRequest()).andReturn(createMock(HttpRequest.class)).atLeastOnce();
replay(command);
}
@AfterTest

View File

@ -1,29 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
====================================================================
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
====================================================================
-->
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>

View File

@ -19,6 +19,8 @@
package org.jclouds.http.internal;
import static org.jclouds.concurrent.ConcurrentUtils.makeListenable;
import static org.jclouds.http.HttpUtils.logRequest;
import static org.jclouds.http.HttpUtils.logResponse;
import java.io.IOException;
import java.util.concurrent.Callable;
@ -35,7 +37,6 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.IOExceptionRetryHandler;
import org.jclouds.http.Payloads;
import org.jclouds.http.handlers.DelegatingErrorHandler;
@ -102,7 +103,7 @@ public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandEx
request.setPayload(Payloads.newPayload(wire.output(request.getPayload()
.getRawContent())));
nativeRequest = convert(request);
HttpUtils.logRequest(headerLog, request, ">>");
logRequest(headerLog, request, ">>");
try {
response = invoke(nativeRequest);
} catch (IOException e) {
@ -117,7 +118,7 @@ public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandEx
}
logger.debug("Receiving response %s: %s", request.hashCode(), response
.getStatusLine());
HttpUtils.logResponse(headerLog, response, "<<");
logResponse(headerLog, response, "<<");
if (response.getContent() != null && wire.enabled())
response.setContent(wire.input(response.getContent()));
int statusCode = response.getStatusCode();

View File

@ -20,6 +20,8 @@ package org.jclouds.rest.functions;
import static org.jclouds.util.Utils.propagateOrNull;
import javax.inject.Singleton;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
@ -30,6 +32,7 @@ import com.google.common.base.Function;
*
* @author Adrian Cole
*/
@Singleton
public class MapHttp4xxCodesToExceptions implements Function<Exception, Object> {
public Object apply(Exception from) {
@ -42,6 +45,8 @@ public class MapHttp4xxCodesToExceptions implements Function<Exception, Object>
throw new AuthorizationException(from);
case 404:
throw new ResourceNotFoundException(from);
case 409:
throw new IllegalStateException(from);
}
}
return propagateOrNull(from);

View File

@ -0,0 +1,88 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.http;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import java.net.URI;
import org.easymock.IArgumentMatcher;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.config.ParserModule;
import org.jclouds.util.Utils;
import com.google.inject.Guice;
/**
*
* @author Adrian Cole
*/
public abstract class BaseHttpErrorHandlerTest {
public static Exception classEq(final Class<? extends Exception> 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;
}
protected abstract Class<? extends HttpErrorHandler> getHandlerClass();
protected void assertCodeMakes(String method, URI uri, int statusCode, String message,
String content, Class<? extends Exception> expected) {
HttpErrorHandler function = Guice.createInjector(new ParserModule()).getInstance(
getHandlerClass());
HttpCommand command = createMock(HttpCommand.class);
HttpRequest request = new HttpRequest(method, uri);
HttpResponse response = new HttpResponse(Utils.toInputStream(content));
response.setStatusCode(statusCode);
response.setMessage(message);
expect(command.getRequest()).andReturn(request).atLeastOnce();
command.setException(classEq(expected));
replay(command);
function.handleError(command, response);
verify(command);
}
}

View File

@ -0,0 +1,80 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.rest.functions;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.Test;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public class MapHttp4xxCodesToExceptionsTest {
@Test
public void test401And403ToAuthorizationException() {
assertCodeMakes(401, AuthorizationException.class);
assertCodeMakes(403, AuthorizationException.class);
}
@Test
public void test404ToResourceNotFoundException() {
assertCodeMakes(404, ResourceNotFoundException.class);
}
@Test
public void test409ToIllegalStateException() {
assertCodeMakes(409, IllegalStateException.class);
}
private void assertCodeMakes(int statuscode, Class<?> expected) {
Function<Exception, Object> function = new MapHttp4xxCodesToExceptions();
HttpResponseException responseException = createMock(HttpResponseException.class);
HttpResponse response = createMock(HttpResponse.class);
expect(response.getStatusCode()).andReturn(statuscode).atLeastOnce();
expect(responseException.getResponse()).andReturn(response).atLeastOnce();
replay(responseException);
replay(response);
try {
function.apply(responseException);
assert false;
} catch (Exception e) {
assertEquals(e.getClass(), expected);
}
verify(responseException);
verify(response);
}
}

View File

@ -50,8 +50,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
/**
* Configures the {@link VCloudComputeServiceContext}; requires
* {@link BaseVCloudComputeClient} bound.
* Configures the {@link VCloudComputeServiceContext}; requires {@link BaseVCloudComputeClient}
* bound.
*
* @author Adrian Cole
*/
@ -72,42 +72,37 @@ public class VCloudGetNodeMetadata {
public static final Pattern TAG_PATTERN_WITH_TEMPLATE = Pattern
.compile("([^-]+)-([0-9a-f][0-9a-f][0-9a-f])[0-9a-f]+");
public static final Pattern TAG_PATTERN_WITHOUT_TEMPLATE = Pattern
.compile("([^-]+)-[0-9]+");
public static final Pattern TAG_PATTERN_WITHOUT_TEMPLATE = Pattern.compile("([^-]+)-[0-9]+");
@Inject
VCloudGetNodeMetadata(VCloudClient client,
VCloudComputeClient computeClient,
VCloudGetNodeMetadata(VCloudClient client, VCloudComputeClient computeClient,
Map<VAppStatus, NodeState> vAppStatusToNodeState, GetExtra getExtra,
FindLocationForResourceInVDC findLocationForResourceInVDC,
Provider<Set<? extends Image>> images) {
this.client = checkNotNull(client, "client");
this.images = checkNotNull(images, "images");
this.getExtra = checkNotNull(getExtra, "getExtra");
this.findLocationForResourceInVDC = checkNotNull(
findLocationForResourceInVDC, "findLocationForResourceInVDC");
this.findLocationForResourceInVDC = checkNotNull(findLocationForResourceInVDC,
"findLocationForResourceInVDC");
this.computeClient = checkNotNull(computeClient, "computeClient");
this.vAppStatusToNodeState = checkNotNull(vAppStatusToNodeState,
"vAppStatusToNodeState");
this.vAppStatusToNodeState = checkNotNull(vAppStatusToNodeState, "vAppStatusToNodeState");
}
public NodeMetadata execute(String id) {
VApp vApp = client.getVApp(id);
if (vApp == null)
return null;
String tag = null;
Image image = null;
Matcher matcher = vApp.getName() != null ? TAG_PATTERN_WITH_TEMPLATE
.matcher(vApp.getName()) : null;
Matcher matcher = vApp.getName() != null ? TAG_PATTERN_WITH_TEMPLATE.matcher(vApp.getName())
: null;
final Location location = findLocationForResourceInVDC.apply(vApp, vApp
.getVDC().getId());
final Location location = findLocationForResourceInVDC.apply(vApp, vApp.getVDC().getId());
if (matcher != null && matcher.find()) {
tag = matcher.group(1);
String templateIdInHexWithoutLeadingZeros = matcher.group(2)
.replaceAll("^[0]+", "");
final String templateId = Integer.parseInt(
templateIdInHexWithoutLeadingZeros, 16)
+ "";
String templateIdInHexWithoutLeadingZeros = matcher.group(2).replaceAll("^[0]+", "");
final String templateId = Integer.parseInt(templateIdInHexWithoutLeadingZeros, 16) + "";
try {
image = Iterables.find(images.get(), new Predicate<Image>() {
@ -119,8 +114,7 @@ public class VCloudGetNodeMetadata {
});
} catch (NoSuchElementException e) {
logger
.warn(
logger.warn(
"could not find a matching image for vapp %s; vapptemplate %s in location %s",
vApp, templateId, location);
}
@ -132,10 +126,9 @@ public class VCloudGetNodeMetadata {
tag = "NOTAG-" + vApp.getName();
}
}
return new NodeMetadataImpl(vApp.getId(), vApp.getName(), vApp.getId(),
location, vApp.getLocation(), ImmutableMap.<String, String> of(),
tag, image, vAppStatusToNodeState.get(vApp.getStatus()),
computeClient.getPublicAddresses(id), computeClient
.getPrivateAddresses(id), getExtra.apply(vApp), null);
return new NodeMetadataImpl(vApp.getId(), vApp.getName(), vApp.getId(), location, vApp
.getLocation(), ImmutableMap.<String, String> of(), tag, image,
vAppStatusToNodeState.get(vApp.getStatus()), computeClient.getPublicAddresses(id),
computeClient.getPrivateAddresses(id), getExtra.apply(vApp), null);
}
}

View File

@ -23,6 +23,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import javax.inject.Singleton;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
@ -41,11 +42,11 @@ import com.google.common.io.Closeables;
* @author Adrian Cole
*
*/
@Singleton
public class ParseVCloudErrorFromHttpResponse implements HttpErrorHandler {
@Resource
protected Logger logger = Logger.NULL;
public static final Pattern RESOURCE_PATTERN = Pattern
.compile(".*/v[^/]*/[0-9]+/([^/]+)/([0-9]+)");
public static final Pattern RESOURCE_PATTERN = Pattern.compile(".*/v[^/]+/([^/]+)/([0-9]+)");
public void handleError(HttpCommand command, HttpResponse response) {
Exception exception = new HttpResponseException(command, response);
@ -56,7 +57,6 @@ public class ParseVCloudErrorFromHttpResponse implements HttpErrorHandler {
case 401:
exception = new AuthorizationException(command.getRequest(), content);
break;
case 403: // TODO temporary as terremark mistakenly uses this for vApp not found.
case 404:
if (!command.getRequest().getMethod().equals("DELETE")) {
String path = command.getRequest().getEndpoint().getPath();

View File

@ -0,0 +1,63 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.handlers;
import java.net.URI;
import org.jclouds.http.BaseHttpErrorHandlerTest;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public class ParseVCloudErrorFromHttpResponseTest extends BaseHttpErrorHandlerTest {
@Test
public void testGet404SetsResourceNotFoundException() {
assertCodeMakes("GET", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
404, "", "", ResourceNotFoundException.class);
}
@Test
public void testDelete404SetsHttpResponseException() {
assertCodeMakes("DELETE", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
404, "", "", HttpResponseException.class);
}
@Test
public void test401SetsAuthorizationException() {
assertCodeMakes("GET", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
401, "", "", AuthorizationException.class);
}
@Override
protected Class<? extends HttpErrorHandler> getHandlerClass() {
return ParseVCloudErrorFromHttpResponse.class;
}
}

View File

@ -81,7 +81,7 @@ import org.jclouds.vcloud.terremark.domain.TerremarkOrganization;
import org.jclouds.vcloud.terremark.domain.VAppConfiguration;
import org.jclouds.vcloud.terremark.endpoints.KeysList;
import org.jclouds.vcloud.terremark.functions.ParseTaskFromLocationHeader;
import org.jclouds.vcloud.terremark.functions.ReturnEmptySetOnUnauthorized;
import org.jclouds.vcloud.terremark.functions.ReturnEmptySetOnResourceNotFoundException;
import org.jclouds.vcloud.terremark.functions.ReturnVoidOnDeleteDefaultIp;
import org.jclouds.vcloud.terremark.options.AddInternetServiceOptions;
import org.jclouds.vcloud.terremark.options.AddNodeOptions;
@ -104,8 +104,7 @@ import com.google.common.util.concurrent.ListenableFuture;
* Provides access to VCloud resources via their REST API.
* <p/>
*
* @see <a href="https://community.vcloudexpress.terremark.com/en-us/discussion_forums/f/60.aspx"
* />
* @see <a href="https://community.vcloudexpress.terremark.com/en-us/discussion_forums/f/60.aspx" />
* @author Adrian Cole
*/
@RequestFilters(SetVCloudTokenCookie.class)
@ -172,8 +171,7 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@XMLResponseParser(VAppHandler.class)
@MapBinder(TerremarkBindInstantiateVAppTemplateParamsToXmlPayload.class)
@Override
ListenableFuture<? extends VApp> instantiateVAppTemplateInVDC(
@PathParam("vDCId") String vDCId,
ListenableFuture<? extends VApp> instantiateVAppTemplateInVDC(@PathParam("vDCId") String vDCId,
@MapPayloadParam("name") @ParamValidators(DnsNameValidator.class) String appName,
@MapPayloadParam("template") @ParamParser(CatalogIdToUri.class) String templateId,
InstantiateVAppTemplateOptions... options);
@ -189,10 +187,8 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@XMLResponseParser(InternetServiceHandler.class)
@MapBinder(AddInternetServiceOptions.class)
ListenableFuture<? extends InternetService> addInternetServiceToVDC(
@PathParam("vDCId") String vDCId,
@MapPayloadParam("name") String serviceName,
@MapPayloadParam("protocol") Protocol protocol,
@MapPayloadParam("port") int port,
@PathParam("vDCId") String vDCId, @MapPayloadParam("name") String serviceName,
@MapPayloadParam("protocol") Protocol protocol, @MapPayloadParam("port") int port,
AddInternetServiceOptions... options);
/**
@ -217,10 +213,8 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@XMLResponseParser(InternetServiceHandler.class)
@MapBinder(AddInternetServiceOptions.class)
ListenableFuture<? extends InternetService> addInternetServiceToExistingIp(
@PathParam("ipId") int existingIpId,
@MapPayloadParam("name") String serviceName,
@MapPayloadParam("protocol") Protocol protocol,
@MapPayloadParam("port") int port,
@PathParam("ipId") int existingIpId, @MapPayloadParam("name") String serviceName,
@MapPayloadParam("protocol") Protocol protocol, @MapPayloadParam("port") int port,
AddInternetServiceOptions... options);
/**
@ -252,8 +246,7 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@Consumes(PUBLICIP_XML)
@XMLResponseParser(InternetServicesHandler.class)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<? extends SortedSet<InternetService>> getPublicIp(
@PathParam("ipId") int ipId);
ListenableFuture<? extends SortedSet<InternetService>> getPublicIp(@PathParam("ipId") int ipId);
/**
* @see TerremarkVCloudClient#getPublicIpsAssociatedWithVDC
@ -298,10 +291,8 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@Consumes(NODESERVICE_XML)
@XMLResponseParser(NodeHandler.class)
@MapBinder(AddNodeOptions.class)
ListenableFuture<? extends Node> addNode(
@PathParam("internetServiceId") int internetServiceId,
@MapPayloadParam("ipAddress") String ipAddress,
@MapPayloadParam("name") String name,
ListenableFuture<? extends Node> addNode(@PathParam("internetServiceId") int internetServiceId,
@MapPayloadParam("ipAddress") String ipAddress, @MapPayloadParam("name") String name,
@MapPayloadParam("port") int port, AddNodeOptions... options);
/**
@ -311,7 +302,7 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@Endpoint(org.jclouds.vcloud.endpoints.VCloudApi.class)
@Path("/extensions/internetService/{internetServiceId}/nodeServices")
@XMLResponseParser(NodesHandler.class)
@ExceptionParser(ReturnEmptySetOnUnauthorized.class)
@ExceptionParser(ReturnEmptySetOnResourceNotFoundException.class)
@Consumes(NODESERVICE_XML)
ListenableFuture<? extends SortedSet<Node>> getNodes(
@PathParam("internetServiceId") int internetServiceId);
@ -391,8 +382,7 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@Path("/extensions/org/{orgId}/keys")
@Consumes(KEYSLIST_XML)
@XMLResponseParser(KeyPairsHandler.class)
ListenableFuture<? extends Set<KeyPair>> listKeyPairsInOrg(
@PathParam("orgId") String orgId);
ListenableFuture<? extends Set<KeyPair>> listKeyPairsInOrg(@PathParam("orgId") String orgId);
/**
* @see TerremarkVCloudClient#generateKeyPairInOrg
@ -404,10 +394,8 @@ public interface TerremarkVCloudAsyncClient extends VCloudAsyncClient {
@Consumes(KEYSLIST_XML)
@XMLResponseParser(KeyPairHandler.class)
@MapBinder(BindCreateKeyToXmlPayload.class)
ListenableFuture<? extends KeyPair> generateKeyPairInOrg(
@PathParam("orgId") String orgId,
@MapPayloadParam("name") String name,
@MapPayloadParam("isDefault") boolean makeDefault);
ListenableFuture<? extends KeyPair> generateKeyPairInOrg(@PathParam("orgId") String orgId,
@MapPayloadParam("name") String name, @MapPayloadParam("isDefault") boolean makeDefault);
/**
* @see TerremarkVCloudClient#getKeyPair

View File

@ -20,6 +20,7 @@ package org.jclouds.vcloud.terremark;
import static org.jclouds.Constants.PROPERTY_API_VERSION;
import static org.jclouds.Constants.PROPERTY_ENDPOINT;
import static org.jclouds.vcloud.reference.VCloudConstants.PROPERTY_VCLOUD_TIMEOUT_TASK_COMPLETED;
import java.util.Properties;
@ -38,6 +39,8 @@ public class TerremarkVCloudPropertiesBuilder extends VCloudPropertiesBuilder {
properties.setProperty(PROPERTY_ENDPOINT, "https://services.vcloudexpress.terremark.com/api");
properties.setProperty("jclouds.dns_name_length_min", "1");
properties.setProperty("jclouds.dns_name_length_max", "15");
// with ssh key injection comes another reboot. allowing more time
properties.setProperty(PROPERTY_VCLOUD_TIMEOUT_TASK_COMPLETED, 360l * 1000l + "");
return properties;
}

View File

@ -18,10 +18,13 @@
*/
package org.jclouds.vcloud.terremark.compute;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getLast;
import static org.jclouds.vcloud.terremark.options.AddInternetServiceOptions.Builder.withDescription;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
@ -35,6 +38,8 @@ import org.jclouds.compute.strategy.PopulateDefaultLoginCredentialsForImageStrat
import org.jclouds.domain.Credentials;
import org.jclouds.vcloud.compute.BaseVCloudComputeClient;
import org.jclouds.vcloud.domain.Task;
import org.jclouds.vcloud.domain.TaskStatus;
import org.jclouds.vcloud.domain.TasksList;
import org.jclouds.vcloud.domain.VApp;
import org.jclouds.vcloud.domain.VAppStatus;
import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions;
@ -45,8 +50,8 @@ import org.jclouds.vcloud.terremark.domain.Protocol;
import org.jclouds.vcloud.terremark.domain.PublicIpAddress;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.internal.ImmutableSet;
/**
* @author Adrian Cole
@ -61,8 +66,7 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
@Inject
protected TerremarkVCloudComputeClient(TerremarkVCloudClient client,
PopulateDefaultLoginCredentialsForImageStrategy credentialsProvider,
@Named("PASSWORD") Provider<String> passwordGenerator,
Predicate<String> successTester,
@Named("PASSWORD") Provider<String> passwordGenerator, Predicate<String> successTester,
Map<VAppStatus, NodeState> vAppStatusToNodeState) {
super(client, successTester, vAppStatusToNodeState);
this.client = client;
@ -71,37 +75,30 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
}
@Override
protected Map<String, String> parseAndValidateResponse(String templateId,
VApp vAppResponse) {
Credentials credentials = credentialsProvider.execute(client
.getVAppTemplate(templateId));
Map<String, String> toReturn = super.parseResponse(templateId,
vAppResponse);
protected Map<String, String> parseAndValidateResponse(String templateId, VApp vAppResponse) {
Credentials credentials = credentialsProvider.execute(client.getVAppTemplate(templateId));
Map<String, String> toReturn = super.parseResponse(templateId, vAppResponse);
toReturn.put("username", credentials.identity);
toReturn.put("password", credentials.credential);
return toReturn;
}
@Override
public Map<String, String> start(String vDCId, String name,
String templateId, InstantiateVAppTemplateOptions options,
int... portsToOpen) {
public Map<String, String> start(String vDCId, String name, String templateId,
InstantiateVAppTemplateOptions options, int... portsToOpen) {
if (options.getDiskSizeKilobytes() != null) {
logger
.warn("trmk does not support resizing the primary disk; unsetting disk size");
logger.warn("trmk does not support resizing the primary disk; unsetting disk size");
}
// we only get IP addresses after "deploy"
if (portsToOpen.length > 0 && !options.shouldBlockOnDeploy())
throw new IllegalArgumentException(
"We cannot open ports on terremark unless we can deploy the vapp");
String password = null;
if (client.getVAppTemplate(templateId).getDescription()
.indexOf("Windows") != -1) {
if (client.getVAppTemplate(templateId).getDescription().indexOf("Windows") != -1) {
password = passwordGenerator.get();
options.getProperties().put("password", password);
}
Map<String, String> response = super.start(vDCId, name, templateId,
options, portsToOpen);
Map<String, String> response = super.start(vDCId, name, templateId, options, portsToOpen);
if (password != null) {
response = new LinkedHashMap<String, String>(response);
response.put("password", password);
@ -114,8 +111,7 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
public String createPublicAddressMappedToPorts(String vAppId, int... ports) {
VApp vApp = client.getVApp(vAppId);
PublicIpAddress ip = null;
String privateAddress = Iterables.getLast(vApp.getNetworkToAddresses()
.values());
String privateAddress = getLast(vApp.getNetworkToAddresses().values());
for (int port : ports) {
InternetService is = null;
Protocol protocol;
@ -135,56 +131,48 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
break;
}
if (ip == null) {
logger.debug(">> creating InternetService in vDC %s:%s:%d", vApp
.getVDC().getId(), protocol, port);
is = client.addInternetServiceToVDC(vApp.getVDC().getId(), vApp
.getName()
+ "-" + port, protocol, port, withDescription(String.format(
"port %d access to serverId: %s name: %s", port,
vApp.getId(), vApp.getName())));
logger.debug(">> creating InternetService in vDC %s:%s:%d", vApp.getVDC().getId(),
protocol, port);
is = client.addInternetServiceToVDC(vApp.getVDC().getId(), vApp.getName() + "-" + port,
protocol, port, withDescription(String.format(
"port %d access to serverId: %s name: %s", port, vApp.getId(), vApp
.getName())));
ip = is.getPublicIpAddress();
} else {
logger.debug(">> adding InternetService %s:%s:%d", ip.getAddress(),
protocol, port);
is = client.addInternetServiceToExistingIp(ip.getId(), vApp
.getName()
+ "-" + port, protocol, port, withDescription(String.format(
"port %d access to serverId: %s name: %s", port,
vApp.getId(), vApp.getName())));
logger.debug(">> adding InternetService %s:%s:%d", ip.getAddress(), protocol, port);
is = client.addInternetServiceToExistingIp(ip.getId(), vApp.getName() + "-" + port,
protocol, port, withDescription(String.format(
"port %d access to serverId: %s name: %s", port, vApp.getId(), vApp
.getName())));
}
logger.debug("<< created InternetService(%s) %s:%s:%d", is.getId(), is
.getPublicIpAddress().getAddress(), is.getProtocol(), is
.getPort());
logger.debug(">> adding Node %s:%d -> %s:%d", is.getPublicIpAddress()
.getAddress(), is.getPort(), privateAddress, port);
Node node = client.addNode(is.getId(), privateAddress, vApp.getName()
+ "-" + port, port);
.getPublicIpAddress().getAddress(), is.getProtocol(), is.getPort());
logger.debug(">> adding Node %s:%d -> %s:%d", is.getPublicIpAddress().getAddress(), is
.getPort(), privateAddress, port);
Node node = client.addNode(is.getId(), privateAddress, vApp.getName() + "-" + port, port);
logger.debug("<< added Node(%s)", node.getId());
}
return ip != null ? ip.getAddress() : null;
}
private Set<PublicIpAddress> deleteInternetServicesAndNodesAssociatedWithVApp(
VApp vApp) {
private Set<PublicIpAddress> deleteInternetServicesAndNodesAssociatedWithVApp(VApp vApp) {
Set<PublicIpAddress> ipAddresses = Sets.newHashSet();
SERVICE: for (InternetService service : client
.getAllInternetServicesInVDC(vApp.getVDC().getId())) {
SERVICE: for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC()
.getId())) {
for (Node node : client.getNodes(service.getId())) {
if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) {
ipAddresses.add(service.getPublicIpAddress());
logger.debug(">> deleting Node(%s) %s:%d -> %s:%d",
node.getId(), service.getPublicIpAddress().getAddress(),
service.getPort(), node.getIpAddress(), node.getPort());
logger.debug(">> deleting Node(%s) %s:%d -> %s:%d", node.getId(), service
.getPublicIpAddress().getAddress(), service.getPort(), node.getIpAddress(),
node.getPort());
client.deleteNode(node.getId());
logger.debug("<< deleted Node(%s)", node.getId());
SortedSet<Node> nodes = client.getNodes(service.getId());
if (nodes.size() == 0) {
logger.debug(">> deleting InternetService(%s) %s:%d", service
.getId(), service.getPublicIpAddress().getAddress(),
service.getPort());
logger.debug(">> deleting InternetService(%s) %s:%d", service.getId(), service
.getPublicIpAddress().getAddress(), service.getPort());
client.deleteInternetService(service.getId());
logger.debug("<< deleted InternetService(%s)", service
.getId());
logger.debug("<< deleted InternetService(%s)", service.getId());
continue SERVICE;
}
}
@ -193,14 +181,13 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
return ipAddresses;
}
private void deletePublicIpAddressesWithNoServicesAttached(
Set<PublicIpAddress> ipAddresses) {
private void deletePublicIpAddressesWithNoServicesAttached(Set<PublicIpAddress> ipAddresses) {
IPADDRESS: for (PublicIpAddress address : ipAddresses) {
SortedSet<InternetService> services = client
.getInternetServicesOnPublicIp(address.getId());
if (services.size() == 0) {
logger.debug(">> deleting PublicIpAddress(%s) %s", address.getId(),
address.getAddress());
logger.debug(">> deleting PublicIpAddress(%s) %s", address.getId(), address
.getAddress());
client.deletePublicIp(address.getId());
logger.debug("<< deleted PublicIpAddress(%s)", address.getId());
continue IPADDRESS;
@ -209,10 +196,9 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
}
/**
* deletes the internet service and nodes associated with the vapp. Deletes
* the IP address, if there are no others using it. Finally, it powers off
* and deletes the vapp. Note that we do not call undeploy, as terremark does
* not support the command.
* deletes the internet service and nodes associated with the vapp. Deletes the IP address, if
* there are no others using it. Finally, it powers off and deletes the vapp. Note that we do not
* call undeploy, as terremark does not support the command.
*/
@Override
public void stop(String id) {
@ -220,11 +206,12 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
Set<PublicIpAddress> ipAddresses = deleteInternetServicesAndNodesAssociatedWithVApp(vApp);
deletePublicIpAddressesWithNoServicesAttached(ipAddresses);
if (vApp.getStatus() != VAppStatus.OFF) {
logger.debug(">> powering off vApp(%s), current status: %s", vApp
.getId(), vApp.getStatus());
Task task = client.powerOffVApp(vApp.getId());
if (!taskTester.apply(task.getId())) {
throw new TaskException("powerOff", vApp, task);
try {
powerOffAndWait(vApp);
} catch (IllegalStateException e) {
logger.warn("<< %s vApp(%s)", e.getMessage(), vApp.getId());
blockOnLastTask(vApp);
powerOffAndWait(vApp);
}
vApp = client.getVApp(id);
logger.debug("<< %s vApp(%s)", vApp.getStatus(), vApp.getId());
@ -234,26 +221,53 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
logger.debug("<< deleted vApp(%s))", vApp.getId());
}
private void powerOffAndWait(VApp vApp) {
logger.debug(">> powering off vApp(%s), current status: %s", vApp.getId(), vApp.getStatus());
Task task = client.powerOffVApp(vApp.getId());
if (!taskTester.apply(task.getId()))
throw new TaskException("powerOff", vApp, task);
}
void blockOnLastTask(VApp vApp) {
TasksList list = client.getDefaultTasksList();
try {
Task lastTask = getLast(filter(list.getTasks(), new Predicate<Task>() {
@Override
public boolean apply(Task input) {
return input.getStatus() == TaskStatus.QUEUED
|| input.getStatus() == TaskStatus.RUNNING;
}
}));
if (!taskTester.apply(lastTask.getId()))
throw new TaskException("powerOff", vApp, lastTask);
} catch (NoSuchElementException ex) {
}
}
/**
* @throws NullPointerException
* if the node is not found
* @returns empty set if the node is not found
*/
@Override
public Set<String> getPrivateAddresses(String id) {
VApp vApp = client.getVApp(id);
if (vApp != null)
return Sets.newHashSet(vApp.getNetworkToAddresses().values());
else
return ImmutableSet.<String> of();
}
/**
* @throws NullPointerException
* if the node is not found
* @returns empty set if the node is not found
*/
@Override
public Set<String> getPublicAddresses(String id) {
VApp vApp = client.getVApp(id);
if (vApp != null) {
Set<String> ipAddresses = Sets.newHashSet();
for (InternetService service : client.getAllInternetServicesInVDC(vApp
.getVDC().getId())) {
for (InternetService service : client.getAllInternetServicesInVDC(vApp.getVDC().getId())) {
for (Node node : client.getNodes(service.getId())) {
if (vApp.getNetworkToAddresses().containsValue(node.getIpAddress())) {
ipAddresses.add(service.getPublicIpAddress().getAddress());
@ -261,5 +275,8 @@ public class TerremarkVCloudComputeClient extends BaseVCloudComputeClient {
}
}
return ipAddresses;
} else {
return ImmutableSet.<String> of();
}
}
}

View File

@ -57,20 +57,15 @@ public class TerremarkVCloudGetNodeMetadataStrategy extends VCloudGetNodeMetadat
@Override
public NodeMetadata execute(String id) {
try {
NodeMetadata node = checkNotNull(getNodeMetadata.execute(checkNotNull(id, "node.id")),
"node: " + id);
NodeMetadata node = getNodeMetadata.execute(checkNotNull(id, "node.id"));
if (node == null)
return null;
if (node.getTag() != null) {
node = installCredentialsFromCache(node);
}
if (node.getCredentials() == null)
node = installDefaultCredentialsFromImage(node);
return node;
} catch (NullPointerException e) {
if (logger.isTraceEnabled())
logger.warn(e, "node %s not found during execution", id);
return null;
}
}
NodeMetadata installCredentialsFromCache(NodeMetadata node) {

View File

@ -24,7 +24,11 @@ import java.net.URI;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.http.HttpErrorHandler;
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.rest.ConfiguresRestClient;
import org.jclouds.util.Utils;
import org.jclouds.vcloud.VCloudAsyncClient;
@ -35,6 +39,7 @@ import org.jclouds.vcloud.terremark.TerremarkVCloudAsyncClient;
import org.jclouds.vcloud.terremark.TerremarkVCloudClient;
import org.jclouds.vcloud.terremark.domain.TerremarkOrganization;
import org.jclouds.vcloud.terremark.endpoints.KeysList;
import org.jclouds.vcloud.terremark.handlers.ParseTerremarkVCloudErrorFromHttpResponse;
import com.google.inject.Provides;
@ -97,4 +102,15 @@ public class TerremarkVCloudRestClientModule
return Utils.toStringAndClose(getClass().getResourceAsStream(
"/terremark/CreateKey.xml"));
}
@Override
protected void bindErrorHandlers() {
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(
ParseTerremarkVCloudErrorFromHttpResponse.class);
bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(
ParseTerremarkVCloudErrorFromHttpResponse.class);
bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(
ParseTerremarkVCloudErrorFromHttpResponse.class);
}
}

View File

@ -24,24 +24,23 @@ import java.util.SortedSet;
import javax.inject.Singleton;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.jclouds.vcloud.terremark.domain.Node;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSortedSet;
/**
* There's a bug where calling get after delete throws an unauthorized exception.
* <p/>
* https://community.vcloudexpress.terremark.com/en-us/discussion_forums/f/60/p/264/876.aspx#876
*
*
* @author Adrian Cole
*/
@Singleton
public class ReturnEmptySetOnUnauthorized implements Function<Exception, SortedSet<Node>> {
public class ReturnEmptySetOnResourceNotFoundException implements
Function<Exception, SortedSet<Node>> {
@SuppressWarnings("unchecked")
public SortedSet<Node> apply(Exception from) {
if (from instanceof AuthorizationException) {
if (from instanceof ResourceNotFoundException) {
return ImmutableSortedSet.<Node> of();
}
return SortedSet.class.cast(propagateOrNull(from));

View File

@ -0,0 +1,79 @@
package org.jclouds.vcloud.terremark.handlers;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
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.logging.Logger;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.jclouds.util.Utils;
import com.google.common.io.Closeables;
/**
* This will parse and set an appropriate exception on the command object.
*
* @author Adrian Cole
*
*/
@Singleton
public class ParseTerremarkVCloudErrorFromHttpResponse implements HttpErrorHandler {
@Resource
protected Logger logger = Logger.NULL;
public static final Pattern RESOURCE_PATTERN = Pattern.compile(".*/v[^/]+/([^/]+)/([0-9]+)");
public void handleError(HttpCommand command, HttpResponse response) {
Exception exception = new HttpResponseException(command, response);
try {
String content = parseErrorFromContentOrNull(command, response);
switch (response.getStatusCode()) {
case 401:
exception = new AuthorizationException(command.getRequest(), content);
break;
case 403: // TODO temporary as terremark mistakenly uses this for vApp not found.
case 404:
if (!command.getRequest().getMethod().equals("DELETE")) {
String path = command.getRequest().getEndpoint().getPath();
Matcher matcher = RESOURCE_PATTERN.matcher(path);
String message;
if (matcher.find()) {
message = String.format("%s %s not found", matcher.group(1), matcher.group(2));
} else {
message = path;
}
exception = new ResourceNotFoundException(message);
}
break;
case 500:
if (response.getMessage().indexOf("because there is a pending task running") != -1)
exception = new IllegalStateException(response.getMessage(), exception);
break;
default:
exception = new HttpResponseException(command, response, content);
}
} finally {
Closeables.closeQuietly(response.getContent());
command.setException(exception);
}
}
String parseErrorFromContentOrNull(HttpCommand command, HttpResponse response) {
if (response.getContent() != null) {
try {
return Utils.toStringAndClose(response.getContent());
} catch (IOException e) {
logger.warn(e, "exception reading error from response", response);
}
}
return null;
}
}

View File

@ -54,7 +54,7 @@ import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions;
import org.jclouds.vcloud.terremark.config.TerremarkVCloudRestClientModule;
import org.jclouds.vcloud.terremark.domain.NodeConfiguration;
import org.jclouds.vcloud.terremark.domain.Protocol;
import org.jclouds.vcloud.terremark.functions.ReturnEmptySetOnUnauthorized;
import org.jclouds.vcloud.terremark.functions.ReturnEmptySetOnResourceNotFoundException;
import org.jclouds.vcloud.terremark.options.AddInternetServiceOptions;
import org.jclouds.vcloud.terremark.options.AddNodeOptions;
import org.jclouds.vcloud.terremark.options.TerremarkInstantiateVAppTemplateOptions;
@ -404,7 +404,7 @@ public class TerremarkVCloudAsyncClientTest extends RestClientTest<TerremarkVClo
assertResponseParserClassEquals(method, request, ParseSax.class);
assertSaxResponseParserClassEquals(method, NodesHandler.class);
assertExceptionParserClassEquals(method, ReturnEmptySetOnUnauthorized.class);
assertExceptionParserClassEquals(method, ReturnEmptySetOnResourceNotFoundException.class);
checkFilters(request);
}

View File

@ -0,0 +1,97 @@
package org.jclouds.vcloud.terremark.handlers;
import java.net.URI;
import org.jclouds.http.BaseHttpErrorHandlerTest;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public class ParseTerremarkVCloudErrorFromHttpResponseTest extends BaseHttpErrorHandlerTest {
@Test
public void testGet403SetsResourceNotFoundException() {
assertCodeMakes(
"GET",
URI.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
403,
"HTTP/1.1 403 Internet Service does not exist in the system. Internet Service was probably deleted by another user. Please refresh and retry the operation",
"", ResourceNotFoundException.class);
}
@Test
public void testGet404SetsResourceNotFoundException() {
assertCodeMakes("GET", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
404, "", "", ResourceNotFoundException.class);
}
@Test
public void testDelete404SetsHttpResponseException() {
assertCodeMakes("DELETE", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
404, "", "", HttpResponseException.class);
}
@Test
public void test401SetsAuthorizationException() {
assertCodeMakes("GET", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
401, "", "", AuthorizationException.class);
}
@Test
public void testbecause_there_is_a_pending_task_runningSetsIllegalStateException() {
assertCodeMakes("GET", URI
.create("https://services.vcloudexpress.terremark.com/api/v0.8a-ext1.6/vdc/32"),
500, "because there is a pending task running",
"because there is a pending task running", IllegalStateException.class);
}
// case 401:
// exception = new AuthorizationException(command.getRequest(), content);
// break;
// case 403: // TODO temporary as terremark mistakenly uses this for vApp not found.
// case 404:
// if (!command.getRequest().getMethod().equals("DELETE")) {
// String path = command.getRequest().getEndpoint().getPath();
// Matcher matcher = RESOURCE_PATTERN.matcher(path);
// String message;
// if (matcher.find()) {
// message = String.format("%s %s not found", matcher.group(1), matcher.group(2));
// } else {
// message = path;
// }
// exception = new ResourceNotFoundException(message);
// }
// break;
// case 401:
// exception = new AuthorizationException(command.getRequest(), content);
// break;
// case 403: // TODO temporary as terremark mistakenly uses this for vApp not found.
// case 404:
// if (!command.getRequest().getMethod().equals("DELETE")) {
// String path = command.getRequest().getEndpoint().getPath();
// Matcher matcher = RESOURCE_PATTERN.matcher(path);
// String message;
// if (matcher.find()) {
// message = String.format("%s %s not found", matcher.group(1), matcher.group(2));
// } else {
// message = path;
// }
// exception = new ResourceNotFoundException(message);
// }
// break;
@Override
protected Class<? extends HttpErrorHandler> getHandlerClass() {
return ParseTerremarkVCloudErrorFromHttpResponse.class;
}
}