From 952a5db51de24bc684505e5fcfacc36e90e129e4 Mon Sep 17 00:00:00 2001 From: Christopher Dancy Date: Mon, 30 Jun 2014 16:01:43 -0400 Subject: [PATCH] JCLOUDS-607: ComputeService.createNodesInGroup throws NPE on FloatingIPApi.create() --- .../functions/AllocateAndAddFloatingIpToNode.java | 95 ++++++++++++++------ .../options/NodeAndNovaTemplateOptions.java | 54 +++++++++++ .../v2_0/compute/options/NovaTemplateOptions.java | 57 +++++++++++- ...eNodesWithGroupEncodedIntoNameThenAddToSet.java | 19 +++- .../AllocateAndAddFloatingIpToNodeExpectTest.java | 56 +++++++++++- .../rest/InsufficientResourcesException.java | 6 +- 6 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NodeAndNovaTemplateOptions.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java index 0a47a35..fe478b9 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNode.java @@ -19,8 +19,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; -import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Resource; @@ -32,6 +33,7 @@ import org.jclouds.compute.domain.NodeMetadataBuilder; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v2_0.NovaApi; +import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.domain.FloatingIP; import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi; @@ -39,6 +41,7 @@ import org.jclouds.rest.InsufficientResourcesException; import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; @@ -49,7 +52,7 @@ import com.google.common.collect.Lists; * A function for adding and allocating an ip to a node */ public class AllocateAndAddFloatingIpToNode implements - Function, AtomicReference> { + Function, AtomicReference> { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) @@ -68,41 +71,79 @@ public class AllocateAndAddFloatingIpToNode implements } @Override - public AtomicReference apply(AtomicReference input) { - checkState(nodeRunning.apply(input), "node never achieved state running %s", input.get()); - NodeMetadata node = input.get(); + public AtomicReference apply(AtomicReference input) { + checkState(nodeRunning.apply(input.get().getNodeMetadata()), "node never achieved state running %s", input.get().getNodeMetadata()); + NodeMetadata node = input.get().getNodeMetadata().get(); // node's location is a host String zoneId = node.getLocation().getParent().getId(); FloatingIPApi floatingIpApi = novaApi.getFloatingIPExtensionForZone(zoneId).get(); + Optional> poolNames = input.get().getNovaTemplateOptions().get().getFloatingIpPoolNames(); + + Optional ip = allocateFloatingIPForNode(floatingIpApi, poolNames, node.getId()); + if (!ip.isPresent()) { + throw new InsufficientResourcesException("Failed to allocate a FloatingIP for node(" + node.getId() + ")"); + } + logger.debug(">> adding floatingIp(%s) to node(%s)", ip.get().getIp(), node.getId()); + floatingIpApi.addToServer(ip.get().getIp(), node.getProviderId()); + input.get().getNodeMetadata().set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.get().getIp())).build()); + floatingIpCache.invalidate(ZoneAndId.fromSlashEncoded(node.getId())); + return input.get().getNodeMetadata(); + } + + /** + * Allocates a FloatingIP for a given Node + * + * @param floatingIpApi FloatingIPApi to create or query for a valid FloatingIP + * @param poolNames optional set of pool names from which we will attempt to allocate an IP from. Most cases this is null + * @param nodeID optional id of the Node we are trying to allocate a FloatingIP for. Used here only for logging purposes + * @return Optional + */ + private Optional allocateFloatingIPForNode(FloatingIPApi floatingIpApi, Optional> poolNames, String nodeID) { + FloatingIP ip = null; + + // 1.) Attempt to allocate from optionally passed poolNames + if (poolNames.isPresent()) { + for (String poolName : poolNames.get()) { + try { + logger.debug(">> allocating floating IP from pool %s for node(%s)", poolName, nodeID); + ip = floatingIpApi.allocateFromPool(poolName); + if (ip != null) + return Optional.of(ip); + } catch (InsufficientResourcesException ire){ + logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ire.getMessage(), poolName, nodeID); + } + } + } + + // 2.) Attempt to allocate, if necessary, via 'create()' call try { - logger.debug(">> allocating or reassigning floating ip for node(%s)", node.getId()); + logger.debug(">> creating floating IP for node(%s)", nodeID); ip = floatingIpApi.create(); - } catch (InsufficientResourcesException e) { - logger.trace("<< [%s] allocating a new floating ip for node(%s)", e.getMessage(), node.getId()); - logger.trace(">> searching for existing, unassigned floating ip for node(%s)", node.getId()); - ArrayList unassignedIps = Lists.newArrayList(Iterables.filter(floatingIpApi.list(), - new Predicate() { - - @Override - public boolean apply(FloatingIP arg0) { - return arg0.getFixedIp() == null; - } - - })); - // try to prevent multiple parallel launches from choosing the same ip. - Collections.shuffle(unassignedIps); - ip = Iterables.getLast(unassignedIps); + if (ip != null) + return Optional.of(ip); + } catch (InsufficientResourcesException ire) { + logger.trace("<< [%s] failed to create floating IP for node(%s)", ire.getMessage(), nodeID); } - logger.debug(">> adding floatingIp(%s) to node(%s)", ip.getIp(), node.getId()); + + // 3.) If no IP was found make final attempt by searching through list of available IP's + logger.trace(">> searching for existing, unassigned floating IP for node(%s)", nodeID); + List unassignedIps = Lists.newArrayList(Iterables.filter(floatingIpApi.list(), + new Predicate() { - floatingIpApi.addToServer(ip.getIp(), node.getProviderId()); - input.set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.getIp())).build()); - floatingIpCache.invalidate(ZoneAndId.fromSlashEncoded(node.getId())); - return input; - } + @Override + public boolean apply(FloatingIP arg0) { + return arg0.getFixedIp() == null; + } + })); + // try to prevent multiple parallel launches from choosing the same ip. + Collections.shuffle(unassignedIps); + ip = Iterables.getLast(unassignedIps); + return Optional.fromNullable(ip); + } + @Override public String toString() { return Objects.toStringHelper("AllocateAndAddFloatingIpToNode").toString(); diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NodeAndNovaTemplateOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NodeAndNovaTemplateOptions.java new file mode 100644 index 0000000..d95e817 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NodeAndNovaTemplateOptions.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.openstack.nova.v2_0.compute.options; + +import java.util.concurrent.atomic.AtomicReference; + +import org.jclouds.compute.domain.NodeMetadata; + +import com.google.common.util.concurrent.Atomics; + +/** + * Simple data-structure for holding a NodeMetadata object along with a + * corresponding NovaTemplateOptions object. + */ +public class NodeAndNovaTemplateOptions { + + private final AtomicReference nodeMetadata; + private final AtomicReference novaTemplateOptions; + + protected NodeAndNovaTemplateOptions(AtomicReference nodeMetadata, AtomicReference novaTemplateOptions) { + this.nodeMetadata = nodeMetadata; + this.novaTemplateOptions = novaTemplateOptions; + } + + public AtomicReference getNodeMetadata() { + return nodeMetadata; + } + + public AtomicReference getNovaTemplateOptions() { + return novaTemplateOptions; + } + + public static NodeAndNovaTemplateOptions newReference(AtomicReference node, AtomicReference options) { + return new NodeAndNovaTemplateOptions(node, options); + } + + public static AtomicReference newAtomicReference(AtomicReference node, AtomicReference options) { + return Atomics.newReference(NodeAndNovaTemplateOptions.newReference(node, options)); + } +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java index b9646d8..fe7aee5 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java @@ -63,6 +63,8 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { if (to instanceof NovaTemplateOptions) { NovaTemplateOptions eTo = NovaTemplateOptions.class.cast(to); eTo.autoAssignFloatingIp(shouldAutoAssignFloatingIp()); + if (getFloatingIpPoolNames().isPresent()) + eTo.floatingIpPoolNames(getFloatingIpPoolNames().get()); if (getSecurityGroupNames().isPresent()) eTo.securityGroupNames(getSecurityGroupNames().get()); eTo.generateKeyPair(shouldGenerateKeyPair()); @@ -80,6 +82,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { } protected boolean autoAssignFloatingIp = false; + protected Optional> floatingIpPoolNames = Optional.absent(); protected Optional> securityGroupNames = Optional.absent(); protected boolean generateKeyPair = false; protected String keyPairName; @@ -96,6 +99,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { return false; NovaTemplateOptions that = NovaTemplateOptions.class.cast(o); return super.equals(that) && equal(this.autoAssignFloatingIp, that.autoAssignFloatingIp) + && equal(this.floatingIpPoolNames, that.floatingIpPoolNames) && equal(this.securityGroupNames, that.securityGroupNames) && equal(this.generateKeyPair, that.generateKeyPair) && equal(this.keyPairName, that.keyPairName) @@ -107,7 +111,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { @Override public int hashCode() { - return Objects.hashCode(super.hashCode(), autoAssignFloatingIp, securityGroupNames, generateKeyPair, keyPairName, userData, diskConfig, configDrive, novaNetworks); + return Objects.hashCode(super.hashCode(), autoAssignFloatingIp, floatingIpPoolNames, securityGroupNames, generateKeyPair, keyPairName, userData, diskConfig, configDrive, novaNetworks); } @Override @@ -115,6 +119,8 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { ToStringHelper toString = super.string(); if (!autoAssignFloatingIp) toString.add("autoAssignFloatingIp", autoAssignFloatingIp); + if (floatingIpPoolNames.isPresent()) + toString.add("floatingIpPoolNames", floatingIpPoolNames.get()); if (securityGroupNames.isPresent()) toString.add("securityGroupNames", securityGroupNames.get()); if (generateKeyPair) @@ -130,12 +136,29 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { public static final NovaTemplateOptions NONE = new NovaTemplateOptions(); /** - * @see #shouldAutoAssignFloatingIp() + * @see #getFloatingIpPoolNames() */ public NovaTemplateOptions autoAssignFloatingIp(boolean enable) { this.autoAssignFloatingIp = enable; return this; } + + /** + * @see #getFloatingIpPoolNames() + */ + public NovaTemplateOptions floatingIpPoolNames(String... floatingIpPoolNames) { + return floatingIpPoolNames(ImmutableSet.copyOf(checkNotNull(floatingIpPoolNames, "floatingIpPoolNames"))); + } + + /** + * @see #getFloatingIpPoolNames() + */ + public NovaTemplateOptions floatingIpPoolNames(Iterable floatingIpPoolNames) { + for (String groupName : checkNotNull(floatingIpPoolNames, "floatingIpPoolNames")) + checkNotNull(emptyToNull(groupName), "all floating-ip-pool-names must be non-empty"); + this.floatingIpPoolNames = Optional.> of(ImmutableSet.copyOf(floatingIpPoolNames)); + return this; + } /** * @see #shouldGenerateKeyPair() @@ -184,13 +207,25 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { } /** + * The floating IP pool name(s) to use when allocating a FloatingIP. Applicable + * only if #shouldAutoAssignFloatingIp() returns true. If not set will attempt to + * use whatever FloatingIP(s) can be found regardless of which pool they originated + * from + * + * @return floating-ip-pool names to use + */ + public Optional> getFloatingIpPoolNames() { + return floatingIpPoolNames; + } + + /** * Specifies the keypair used to run instances with * @return the keypair to be used */ public String getKeyPairName() { return keyPairName; } - + /** *

Note

* @@ -248,6 +283,22 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { } /** + * @see #getFloatingIpPoolNames() + */ + public NovaTemplateOptions floatingIpPoolNames(String... floatingIpPoolNames) { + NovaTemplateOptions options = new NovaTemplateOptions(); + return NovaTemplateOptions.class.cast(options.floatingIpPoolNames(floatingIpPoolNames)); + } + + /** + * @see #getFloatingIpPoolNames() + */ + public NovaTemplateOptions floatingIpPoolNames(Iterable floatingIpPoolNames) { + NovaTemplateOptions options = new NovaTemplateOptions(); + return NovaTemplateOptions.class.cast(options.floatingIpPoolNames(floatingIpPoolNames)); + } + + /** * @see NovaTemplateOptions#shouldGenerateKeyPair() */ public static NovaTemplateOptions generateKeyPair(boolean enable) { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 32a41ca..2c1f035 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -42,16 +42,19 @@ import org.jclouds.compute.strategy.ListNodesStrategy; import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.compute.functions.AllocateAndAddFloatingIpToNode; +import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.domain.KeyPair; import org.jclouds.openstack.nova.v2_0.domain.zonescoped.SecurityGroupInZone; import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneAndName; import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneSecurityGroupNameAndPorts; +import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.cache.LoadingCache; import com.google.common.collect.Multimap; import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.Atomics; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; @@ -149,13 +152,21 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT final String name, Template template) { ListenableFuture> future = super.createNodeInGroupWithNameAndTemplate(group, name, template); - NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions()); - + final NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions()); if (templateOptions.shouldAutoAssignFloatingIp()) { - return Futures.transform(future, createAndAddFloatingIpToNode, userExecutor); + + ListenableFuture> nodeAndNovaTemplateOptions = Futures.transform(future, + new Function, AtomicReference>() { + + @Override + public AtomicReference apply(AtomicReference input) { + return NodeAndNovaTemplateOptions.newAtomicReference(input, Atomics.newReference(templateOptions)); + } + } + ); + return Futures.transform(nodeAndNovaTemplateOptions, createAndAddFloatingIpToNode, userExecutor); } else { return future; } } - } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java index 0cdc3d5..1cb9b80 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java @@ -31,6 +31,8 @@ import org.jclouds.domain.LocationScope; import org.jclouds.domain.LoginCredentials; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions; +import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.internal.BaseNovaComputeServiceExpectTest; import org.testng.annotations.Test; @@ -53,7 +55,8 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer final NodeMetadata node = new NodeMetadataBuilder().id("az-1.region-a.geo-1/71592").providerId("71592").location( host).name("Server 71592").status(Status.RUNNING).privateAddresses(ImmutableSet.of("10.4.27.237")) .credentials(LoginCredentials.builder().password("foo").build()).build(); - + final NovaTemplateOptions options = NovaTemplateOptions.Builder.autoAssignFloatingIp(false); + HttpRequest createFloatingIP = HttpRequest.builder().method("POST").endpoint( URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-floating-ips")).headers( ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", @@ -75,9 +78,13 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer .getInstance(AllocateAndAddFloatingIpToNode.class); AtomicReference nodeRef = Atomics.newReference(node); - fn.apply(nodeRef); + AtomicReference optionsRef = Atomics.newReference(options); + AtomicReference nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef); + + fn.apply(nodeNovaRef); NodeMetadata node1 = nodeRef.get(); assertNotNull(node1); + assertNotNull(optionsRef.get()); assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.3")); assertEquals(node1.getCredentials(), node.getCredentials()); @@ -94,7 +101,7 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer return addFloatingIPRequest; } - public void testAllocateWhenAllocationFailsLookupUnusedIpAddToServerAndUpdatesNodeMetadata() throws Exception { + public void testAllocateWhenAllocationFailsOn400LookupUnusedIpAddToServerAndUpdatesNodeMetadata() throws Exception { HttpResponse createFloatingIPResponse = HttpResponse .builder() .statusCode(400) @@ -122,10 +129,51 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer .getInstance(AllocateAndAddFloatingIpToNode.class); AtomicReference nodeRef = Atomics.newReference(node); - fn.apply(nodeRef); + AtomicReference optionsRef = Atomics.newReference(options); + AtomicReference nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef); + + fn.apply(nodeNovaRef); NodeMetadata node1 = nodeRef.get(); assertNotNull(node1); + assertNotNull(optionsRef.get()); assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.5")); + } + + public void testAllocateWhenAllocationFailsOn404LookupUnusedIpAddToServerAndUpdatesNodeMetadata() throws Exception { + HttpResponse createFloatingIPResponse = HttpResponse + .builder() + .statusCode(404) + .payload( + payloadFromStringWithContentType( + "{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 404}}", + "application/json")).build(); + + HttpRequest list = HttpRequest.builder().method("GET").endpoint( + URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-floating-ips")).headers( + ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", + authToken).build()).build(); + + HttpResponse listResponseForUnassigned = HttpResponse.builder().statusCode(200).payload( + payloadFromResource("/floatingip_list.json")).build(); + + HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.5"); + + AllocateAndAddFloatingIpToNode fn = requestsSendResponses( + ImmutableMap. builder().put(keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess).put(extensionsOfNovaRequest, extensionsOfNovaResponse).put( + createFloatingIP, createFloatingIPResponse) + .put(addFloatingIPRequest, addFloatingIPResponse).put(list, + listResponseForUnassigned).build()).getContext().utils().injector() + .getInstance(AllocateAndAddFloatingIpToNode.class); + + AtomicReference nodeRef = Atomics.newReference(node); + AtomicReference optionsRef = Atomics.newReference(options); + AtomicReference nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef); + fn.apply(nodeNovaRef); + NodeMetadata node1 = nodeRef.get(); + assertNotNull(node1); + assertNotNull(optionsRef.get()); + assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.5")); } } diff --git a/core/src/main/java/org/jclouds/rest/InsufficientResourcesException.java b/core/src/main/java/org/jclouds/rest/InsufficientResourcesException.java index 793ec31..0bbbcfa 100644 --- a/core/src/main/java/org/jclouds/rest/InsufficientResourcesException.java +++ b/core/src/main/java/org/jclouds/rest/InsufficientResourcesException.java @@ -26,7 +26,11 @@ public class InsufficientResourcesException extends RuntimeException { public InsufficientResourcesException() { super(); } - + + public InsufficientResourcesException(String arg0) { + super(arg0); + } + public InsufficientResourcesException(String arg0, Throwable arg1) { super(arg0, arg1); } -- 1.7.9.msysgit.0