diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/OverLimitParser.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/OverLimitParser.java
new file mode 100644
index 0000000000..85567e12ea
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/OverLimitParser.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v2_0.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+
+import org.jclouds.json.Json;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ *
+ * The expected body contains the time as in this (real) response
+ *
+ *
+ * {
+ * "overLimit" : {
+ * "code" : 413,
+ * "message" : "OverLimit Retry...",
+ * "details" : "Error Details...",
+ * "retryAt" : "2012-11-14T21:51:28UTC"
+ * }
+ * }
+ *
+ *
+ * or
+ *
+ *
+ * {
+ * "overLimit": {
+ * "message": "This request was rate-limited.",
+ * "code": 413,
+ * "retryAfter": "54",
+ * "details": "Only 1 POST request(s) can be made to \"*\" every minute."
+ * }
+ * }
+ *
+ *
+ * @author Adrian Cole, Steve Loughran
+ *
+ */
+public class OverLimitParser implements Function> {
+
+ @Resource
+ private Logger logger = Logger.NULL;
+ private final Json json;
+
+ @Inject
+ public OverLimitParser(Json json) {
+ this.json = checkNotNull(json, "json");
+ }
+
+ private static class Holder {
+ Map overLimit = ImmutableMap.of();
+ }
+
+ /**
+ * parses or returns an empty map.
+ */
+ @Override
+ public Map apply(String in) {
+ try {
+ return json.fromJson(in, OverLimitParser.Holder.class).overLimit;
+ } catch (RuntimeException e) {
+ // an error was raised during parsing -which can include badly
+ // formatted fields.
+ logger.error("Failed to parse " + in + "", e);
+ return ImmutableMap.of();
+ }
+ }
+}
\ No newline at end of file
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
index 5afd04196c..6592c7d6b7 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandler.java
@@ -18,37 +18,72 @@
*/
package org.jclouds.openstack.nova.v2_0.handlers;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Strings.emptyToNull;
+import static com.google.common.collect.Maps.filterKeys;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
import javax.inject.Singleton;
+import org.jclouds.date.DateCodecFactory;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
+import org.jclouds.http.functions.HeaderToRetryAfterException;
+import org.jclouds.logging.Logger;
+import org.jclouds.openstack.nova.v2_0.functions.OverLimitParser;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.InsufficientResourcesException;
import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.rest.RetryAfterException;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableSet;
/**
* This will parse and set an appropriate exception on the command object.
*
- * @author Adrian Cole
+ * @author Adrian Cole, Steve Loughran
*
*/
// TODO: is there error spec someplace? let's type errors, etc.
@Singleton
public class NovaErrorHandler implements HttpErrorHandler {
+ @Resource
+ protected Logger logger = Logger.NULL;
+ protected final HeaderToRetryAfterException retryAfterParser;
+ protected final OverLimitParser overLimitParser;
+
+ protected NovaErrorHandler(HeaderToRetryAfterException retryAfterParser, OverLimitParser overLimitParser) {
+ this.retryAfterParser = checkNotNull(retryAfterParser, "retryAfterParser");
+ this.overLimitParser = checkNotNull(overLimitParser, "overLimitParser");
+ }
+
+ /**
+ * in current format, retryAt has a value of {@code 2012-11-14T21:51:28UTC}, which is an ISO-8601 seconds (not milliseconds) format.
+ */
+ @Inject
+ public NovaErrorHandler(DateCodecFactory factory, OverLimitParser overLimitParser) {
+ this(HeaderToRetryAfterException.create(Ticker.systemTicker(), factory.iso8601Seconds()), overLimitParser);
+ }
+
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;
+ String content = data != null ? emptyToNull(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());
+ Exception exception = content != null ? new HttpResponseException(command, response, content)
+ : new HttpResponseException(command, response);
+ String requestLine = command.getCurrentRequest().getRequestLine();
+ String message = content != null ? content : String.format("%s -> %s", requestLine, response.getStatusLine());
switch (response.getStatusCode()) {
case 400:
if (message.indexOf("quota exceeded") != -1)
@@ -68,10 +103,29 @@ public class NovaErrorHandler implements HttpErrorHandler {
}
break;
case 413:
- exception = new InsufficientResourcesException(message, exception);
- break;
+ if (content == null) {
+ exception = new InsufficientResourcesException(message, exception);
+ break;
+ }
+ exception = parseAndBuildRetryException(content, message, exception);
}
command.setException(exception);
}
+ /**
+ * Build an exception from the response. If it contains the JSON payload then
+ * that is parsed to create a {@link RetryAfterException}, otherwise a
+ * {@link InsufficientResourcesException} is returned
+ *
+ */
+ private Exception parseAndBuildRetryException(String json, String message, Exception exception) {
+ Set retryFields = ImmutableSet.of("retryAfter", "retryAt");
+ for (String value : filterKeys(overLimitParser.apply(json), in(retryFields)).values()) {
+ Optional retryException = retryAfterParser.tryCreateRetryAfterException(exception, value);
+ if (retryException.isPresent())
+ return retryException.get();
+ }
+ return new InsufficientResourcesException(message, exception);
+ }
+
}
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/NovaErrorHandlerTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/NovaErrorHandlerTest.java
deleted file mode 100644
index 2691715488..0000000000
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/NovaErrorHandlerTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/**
- * Licensed to jclouds, Inc. (jclouds) under one or more
- * contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. jclouds licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.jclouds.openstack.nova.v2_0;
-
-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.openstack.nova.v2_0.handlers.NovaErrorHandler;
-import org.jclouds.rest.AuthorizationException;
-import org.jclouds.rest.InsufficientResourcesException;
-import org.jclouds.rest.ResourceNotFoundException;
-import org.testng.annotations.Test;
-
-import com.google.inject.Guice;
-
-/**
- *
- * @author Adrian Cole
- */
-@Test(groups = { "unit" })
-public class NovaErrorHandlerTest {
-
- @Test
- public void test401MakesAuthorizationException() {
- assertCodeMakes("GET", URI.create("https://api.openstack.nova.com/foo"), 401, "", "Unauthorized",
- AuthorizationException.class);
- }
-
- @Test
- public void test400MakesIllegalStateExceptionOnQuotaExceededOnNoFixedIps() {
- // should wait until ips are associated w/the server
- assertCodeMakes(
- "POST",
- URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/servers/71554/action"),
- 400,
- "HTTP/1.1 400 Bad Request",
- "{\"badRequest\": {\"message\": \"instance |71554| has no fixed_ips. unable to associate floating ip\", \"code\": 400}}",
- IllegalStateException.class);
- }
-
- @Test
- public void test400MakesIllegalStateExceptionOnAlreadyExists() {
- assertCodeMakes(
- "POST",
- URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/servers"),
- 400,
- "HTTP/1.1 400 Bad Request",
- "{\"badRequest\": {\"message\": \"Server with the name 'test' already exists\", \"code\": 400}}",
- IllegalStateException.class);
- }
-
- @Test
- public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() {
- assertCodeMakes(
- "POST",
- URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/os-floating-ips"),
- 400,
- "HTTP/1.1 400 Bad Request",
- "{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 400}}",
- InsufficientResourcesException.class);
- }
-
- @Test
- public void test413MakesInsufficientResourcesException() {
- assertCodeMakes(
- "POST",
- URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/os-volumes"),
- 413,
- "HTTP/1.1 413 Request Entity Too Large",
- "{\"badRequest\": {\"message\": \"Volume quota exceeded. You cannot create a volume of size 1G\", \"code\": 413, \"retryAfter\": 0}}",
- InsufficientResourcesException.class);
- }
-
- @Test
- public void test404MakesResourceNotFoundException() {
- assertCodeMakes("GET", URI.create("https://api.openstack.nova.com/foo"), 404, "", "Not Found",
- ResourceNotFoundException.class);
- }
-
- private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
- Class extends Exception> expected) {
- assertCodeMakes(method, uri, statusCode, message, "text/json", content, expected);
- }
-
- private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType,
- String content, Class extends Exception> expected) {
-
- NovaErrorHandler function = Guice.createInjector().getInstance(NovaErrorHandler.class);
-
- HttpCommand command = createMock(HttpCommand.class);
- HttpRequest request = HttpRequest.builder().method(method).endpoint(uri).build();
- HttpResponse response = HttpResponse.builder().statusCode(statusCode).message(message).payload(content).build();
- response.getPayload().getContentMetadata().setContentType(contentType);
-
- expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
- command.setException(classEq(expected));
-
- replay(command);
-
- function.handleError(command, response);
-
- verify(command);
- }
-
- public static Exception classEq(final Class 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;
- }
-
-}
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandlerTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandlerTest.java
new file mode 100644
index 0000000000..08c8fe85c0
--- /dev/null
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/handlers/NovaErrorHandlerTest.java
@@ -0,0 +1,276 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v2_0.handlers;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.date.DateCodec;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceIso8601SecondsCodec;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.HeaderToRetryAfterException;
+import org.jclouds.json.internal.GsonWrapper;
+import org.jclouds.openstack.nova.v2_0.functions.OverLimitParser;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.InsufficientResourcesException;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.rest.RetryAfterException;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Ticker;
+import com.google.gson.Gson;
+
+/**
+ *
+ * @author Adrian Cole, Steve Loughran
+ */
+@Test(groups = { "unit" })
+public class NovaErrorHandlerTest {
+
+ private HttpCommand command;
+
+ @BeforeTest
+ void setupCommand(){
+ command = command();
+ }
+
+ @Test
+ public void test401MakesAuthorizationException() {
+ fn.handleError(command, HttpResponse.builder().statusCode(401).message("Unauthorized").build());
+
+ assertEquals(command.getException().getClass(), AuthorizationException.class);
+ assertEquals(command.getException().getMessage(),
+ "POST https://nova/v1.1/servers HTTP/1.1 -> HTTP/1.1 401 Unauthorized");
+ }
+
+ @Test
+ public void test404MakesResourceNotFoundException() {
+ fn.handleError(command, HttpResponse.builder().statusCode(404).message("Not Found").build());
+
+ assertEquals(command.getException().getClass(), ResourceNotFoundException.class);
+ assertEquals(command.getException().getMessage(),
+ "POST https://nova/v1.1/servers HTTP/1.1 -> HTTP/1.1 404 Not Found");
+ }
+
+ // should wait until ips are associated w/the server
+ HttpResponse noFixedIps = HttpResponse.builder().statusCode(400)
+ .message("HTTP/1.1 400 Bad Request")
+ .payload("{\"badRequest\": {\"message\": "+
+ "\"instance |71554| has no fixed_ips. unable to associate floating ip\", \"code\": 400}}")
+ .build();
+
+ @Test
+ public void test400MakesIllegalStateExceptionOnQuotaExceededOnNoFixedIps() {
+ fn.handleError(command, noFixedIps);
+
+ assertEquals(command.getException().getClass(), IllegalStateException.class);
+ assertEquals(command.getException().getMessage(), noFixedIps.getPayload().getRawContent());
+ }
+
+ HttpResponse alreadyExists = HttpResponse.builder().statusCode(400)
+ .message("HTTP/1.1 400 Bad Request")
+ .payload("{\"badRequest\": {\"message\": \"Server with the name 'test' already exists\", \"code\": 400}}")
+ .build();
+
+ @Test
+ public void test400MakesIllegalStateExceptionOnAlreadyExists() {
+ fn.handleError(command, alreadyExists);
+
+ assertEquals(command.getException().getClass(), IllegalStateException.class);
+ assertEquals(command.getException().getMessage(), alreadyExists.getPayload().getRawContent());
+ }
+
+ HttpResponse quotaExceeded = HttpResponse.builder().statusCode(400)
+ .message("HTTP/1.1 400 Bad Request")
+ .payload("{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. " +
+ "You cannot create any more addresses\", \"code\": 400}}")
+ .build();
+
+ @Test
+ public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() {
+ fn.handleError(command, quotaExceeded);
+
+ assertEquals(command.getException().getClass(), InsufficientResourcesException.class);
+ assertEquals(command.getException().getMessage(), quotaExceeded.getPayload().getRawContent());
+ }
+
+ HttpResponse tooLarge = HttpResponse.builder().statusCode(413)
+ .message("HTTP/1.1 413 Request Entity Too Large")
+ .payload("{\"badRequest\": {\"message\": \"Volume quota exceeded. You cannot create a volume of size 1G\", " +
+ "\"code\": 413, \"retryAfter\": 0}}")
+ .build();
+
+ @Test
+ public void test413MakesInsufficientResourcesException() {
+ fn.handleError(command, tooLarge);
+
+ assertEquals(command.getException().getClass(), InsufficientResourcesException.class);
+ assertEquals(command.getException().getMessage(), tooLarge.getPayload().getRawContent());
+ }
+
+ /**
+ * Reponse received from Rackspace UK on November 14, 2012.
+ */
+ HttpResponse retryAt = HttpResponse.builder().statusCode(413)
+ .message("HTTP/1.1 413 Request Entity Too Large")
+ .payload("{ 'overLimit' : { 'code' : 413,"
+ + " 'message' : 'OverLimit Retry...', "
+ + " 'details' : 'Error Details...',"
+ + " 'retryAt' : '2012-11-14T21:51:28UTC' }}")
+ .build();
+
+ @Test
+ public void test413WithRetryAtExceptionParsesDelta() {
+ fn.handleError(command, retryAt);
+
+ assertEquals(command.getException().getClass(), RetryAfterException.class);
+ assertEquals(command.getException().getMessage(), "retry in 3600 seconds");
+ }
+
+ /**
+ * Folsom response. This contains a delta in seconds to retry after, not a
+ * fixed time.
+ *
+ */
+ HttpResponse retryAfter = HttpResponse.builder().statusCode(413)
+ .message("HTTP/1.1 413 Request Entity Too Large")
+ .payload("{ 'overLimit': { 'message': 'This request was rate-limited.', "
+ + " 'retryAfter': '54', "
+ + " 'details': 'Only 1 POST request(s) can be made to \\'*\\' every minute.'" + " }}")
+ .build();
+
+ @Test
+ public void test413WithRetryAfterExceptionFolsom() {
+ fn.handleError(command, retryAfter);
+
+ assertEquals(command.getException().getClass(), RetryAfterException.class);
+ assertEquals(command.getException().getMessage(), "retry in 54 seconds");
+ }
+
+ /**
+ * Folsom response with a retryAt field inserted -at a different date. This
+ * can be used to verify that the retryAfter field is picked up first
+ */
+ HttpResponse retryAfterTrumps = HttpResponse.builder().statusCode(413)
+ .message("HTTP/1.1 413 Request Entity Too Large")
+ .payload("{ 'overLimit': {"
+ + " 'message': 'This request was rate-limited.', "
+ + " 'retryAfter': '54', "
+ + " 'retryAt' : '2012-11-14T21:51:28UTC',"
+ + " 'details': 'Only 1 POST request(s) can be made to \\'*\\' every minute.' }}")
+ .build();
+
+ @Test
+ public void test413WithRetryAfterTrumpsRetryAt() {
+ fn.handleError(command, retryAfterTrumps);
+
+ assertEquals(command.getException().getClass(), RetryAfterException.class);
+ assertEquals(command.getException().getMessage(), "retry in 54 seconds");
+ }
+
+ HttpResponse badRetryAt = HttpResponse.builder().statusCode(413)
+ .message("HTTP/1.1 413 Request Entity Too Large")
+ .payload("{ 'overLimit' : { 'code' : 413,"
+ + " 'message' : 'OverLimit Retry...', "
+ + " 'details' : 'Error Details...',"
+ + " 'retryAt' : '2012-11-~~~:51:28UTC' }}")
+ .build();
+
+ @Test
+ public void test413WithBadRetryAtFormatFallsBack() {
+ fn.handleError(command, badRetryAt);
+
+ assertEquals(command.getException().getClass(), InsufficientResourcesException.class);
+ assertEquals(command.getException().getMessage(), badRetryAt.getPayload().getRawContent());
+ }
+
+
+ DateCodec iso8601Seconds = new DateServiceIso8601SecondsCodec(new SimpleDateFormatDateService());
+
+ Ticker y2k = new Ticker(){
+
+ @Override
+ public long read() {
+ return TimeUnit.MILLISECONDS.toNanos(iso8601Seconds.toDate("2012-11-14T20:51:28UTC").getTime());
+ }
+
+ };
+
+ NovaErrorHandler fn = new NovaErrorHandler(HeaderToRetryAfterException.create(y2k, iso8601Seconds),
+ new OverLimitParser(new GsonWrapper(new Gson())));
+
+ private HttpCommand command() {
+ return new HttpCommand() {
+
+ private Exception exception;
+
+ @Override
+ public int getRedirectCount() {
+ return 0;
+ }
+
+ @Override
+ public int incrementRedirectCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean isReplayable() {
+ return false;
+ }
+
+ @Override
+ public Exception getException() {
+ return exception;
+ }
+
+ @Override
+ public int getFailureCount() {
+ return 0;
+ }
+
+ @Override
+ public int incrementFailureCount() {
+ return 0;
+ }
+
+ @Override
+ public void setException(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public HttpRequest getCurrentRequest() {
+ return HttpRequest.builder().method("POST").endpoint("https://nova/v1.1/servers").build();
+ }
+
+ @Override
+ public void setCurrentRequest(HttpRequest request) {
+
+ }
+
+ };
+ }
+
+}