[JCLOUDS-1382] improve usage of Neutron capabilities from Nova

- move Neutron FloatingIP API from extension to feature
- introduce FloatingIpForServer as abstract domain object to represents Nova and Neutron objects
- manage floating ip cache and its invalidation
This commit is contained in:
andreaturli 2018-02-14 16:03:57 +01:00
parent 88c84af878
commit 50ae6828f5
23 changed files with 686 additions and 181 deletions

View File

@ -22,7 +22,7 @@ import java.util.Set;
import javax.ws.rs.Path;
import org.jclouds.location.Region;
import org.jclouds.openstack.neutron.v2.extensions.FloatingIPApi;
import org.jclouds.openstack.neutron.v2.features.FloatingIPApi;
import org.jclouds.openstack.neutron.v2.extensions.RouterApi;
import org.jclouds.openstack.neutron.v2.features.SecurityGroupApi;
import org.jclouds.openstack.neutron.v2.extensions.lbaas.v1.LBaaSApi;
@ -88,6 +88,12 @@ public interface NeutronApi extends Closeable {
@Delegate
SecurityGroupApi getSecurityGroupApi(@EndpointParam(parser = VersionAwareRegionToEndpoint.class) String region);
/**
* Provides access to Floating IP features.
*/
@Delegate
FloatingIPApi getFloatingIPApi(@EndpointParam(parser = VersionAwareRegionToEndpoint.class) String region);
/**
* Provides access to Router features.
*
@ -98,16 +104,6 @@ public interface NeutronApi extends Closeable {
@Delegate
Optional<RouterApi> getRouterApi(@EndpointParam(parser = VersionAwareRegionToEndpoint.class) String region);
/**
* Provides access to Floating IP features.
*
* <h3>NOTE</h3>
* This API is an extension that may or may not be present in your OpenStack cloud. Use the Optional return type
* to determine if it is present.
*/
@Delegate
Optional<FloatingIPApi> getFloatingIPApi(@EndpointParam(parser = VersionAwareRegionToEndpoint.class) String region);
/**
* Provides access to LBaaS features.
*

View File

@ -34,6 +34,8 @@ public class FloatingIP {
private String id;
@Named("router_id")
private String routerId;
@Named("project_id")
private String projectId;
@Named("tenant_id")
private String tenantId;
// Only mandatory attribute when creating
@ -45,21 +47,25 @@ public class FloatingIP {
private String floatingIpAddress;
@Named("port_id")
private String portId;
@Named("availability_zone")
private String availabilityZone;
/**
* Deserialization constructor
*/
@ConstructorProperties({"id", "router_id", "tenant_id", "floating_network_id", "fixed_ip_address",
"floating_ip_address", "port_id"})
private FloatingIP(String id, String routerId, String tenantId, String floatingNetworkId, String fixedIpAddress,
String floatingIpAddress, String portId) {
@ConstructorProperties({"id", "router_id", "project_id", "tenant_id", "floating_network_id", "fixed_ip_address",
"floating_ip_address", "port_id", "availability_zone"})
private FloatingIP(String id, String routerId, String projectId, String tenantId, String floatingNetworkId, String fixedIpAddress,
String floatingIpAddress, String portId, String availabilityZone) {
this.id = id;
this.routerId = routerId;
this.projectId = projectId;
this.tenantId = tenantId;
this.floatingNetworkId = floatingNetworkId;
this.fixedIpAddress = fixedIpAddress;
this.floatingIpAddress = floatingIpAddress;
this.portId = portId;
this.availabilityZone = availabilityZone;
}
private FloatingIP() {}
@ -68,8 +74,8 @@ public class FloatingIP {
* @param floatingIP The floating IP to copy from
*/
private FloatingIP(FloatingIP floatingIP) {
this(floatingIP.id, floatingIP.routerId, floatingIP.tenantId, floatingIP.floatingNetworkId,
floatingIP.fixedIpAddress, floatingIP.floatingIpAddress, floatingIP.portId);
this(floatingIP.id, floatingIP.routerId, floatingIP.projectId, floatingIP.tenantId, floatingIP.floatingNetworkId,
floatingIP.fixedIpAddress, floatingIP.floatingIpAddress, floatingIP.portId, floatingIP.availabilityZone);
}
/**
@ -88,6 +94,14 @@ public class FloatingIP {
return routerId;
}
/**
* @return the project id of the Floating IP
*/
@Nullable
public String getProjectId() {
return projectId;
}
/**
* @return the tenant id of the Floating IP
*/
@ -128,6 +142,14 @@ public class FloatingIP {
return portId;
}
/**
* @return the availability zone for this floating IP
*/
@Nullable
public String getAvailabilityZone() {
return availabilityZone;
}
@Override
public boolean equals(Object o) {
if (this == o)
@ -139,17 +161,20 @@ public class FloatingIP {
return Objects.equal(this.id, that.id) &&
Objects.equal(this.routerId, that.routerId) &&
Objects.equal(this.projectId, that.projectId) &&
Objects.equal(this.tenantId, that.tenantId) &&
Objects.equal(this.floatingNetworkId, that.floatingNetworkId) &&
Objects.equal(this.fixedIpAddress, that.fixedIpAddress) &&
Objects.equal(this.floatingIpAddress, that.floatingIpAddress) &&
Objects.equal(this.portId, that.portId);
Objects.equal(this.portId, that.portId) &&
Objects.equal(this.availabilityZone, that.availabilityZone);
}
@Override
public int hashCode() {
return Objects.hashCode(id, routerId, tenantId, floatingNetworkId, fixedIpAddress, floatingIpAddress,
portId);
return Objects.hashCode(id, routerId, projectId, tenantId, floatingNetworkId, fixedIpAddress, floatingIpAddress,
portId, availabilityZone);
}
@Override
@ -157,11 +182,13 @@ public class FloatingIP {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("routerId", routerId)
.add("projectId", projectId)
.add("tenantId", tenantId)
.add("floatingNetworkId", floatingNetworkId)
.add("fixedIpAddress", fixedIpAddress)
.add("floatingIpAddress", floatingIpAddress)
.add("portId", portId)
.add("availabilityZone", availabilityZone)
.toString();
}
@ -191,6 +218,17 @@ public class FloatingIP {
protected abstract ParameterizedBuilderType self();
/**
* Provide the project ID for this Floating IP.
*
* @return the Builder.
* @see FloatingIP#getProjectId() ()
*/
public ParameterizedBuilderType projectId(String projectId) {
floatingIP.projectId = projectId;
return self();
}
/**
* Provide the tenantId for this Floating IP. Admin-only.
* When keystone is enabled, it is not mandatory to specify tenant_id for resources in create requests, as the
@ -249,6 +287,18 @@ public class FloatingIP {
floatingIP.portId = portId;
return self();
}
/**
* Provides the availability zone for this Floating IP.
*
* @return the Builder.
* @see FloatingIP#getAvailabilityZone()
*/
public ParameterizedBuilderType availabilityZone(String availabilityZone) {
floatingIP.availabilityZone = availabilityZone;
return self();
}
}
public static class CreateBuilder extends Builder<CreateBuilder> {

View File

@ -47,6 +47,8 @@ public class Network {
private Boolean shared;
@Named("tenant_id")
private String tenantId;
@Named("availability_zone")
private String availabilityZone;
// providernet.py: Provider Networks Extension
@Named("provider:network_type")
@ -85,12 +87,12 @@ public class Network {
@Named("flavor:network")
private String networkFlavor;
@ConstructorProperties({"id", "status", "subnets", "name", "admin_state_up", "shared", "tenant_id",
@ConstructorProperties({"id", "status", "subnets", "name", "admin_state_up", "shared", "tenant_id", "availability_zone",
"provider:network_type", "provider:physical_network", "provider:segmentation_id", "router:external",
"port_security_enabled", "n1kv:profile_id", "n1kv:multicast_ip", "n1kv:segment_add", "n1kv:segment_del",
"n1kv:member_segments", "segments", "flavor:network"})
private Network(String id, NetworkStatus status, ImmutableSet<String> subnets, String name, Boolean adminStateUp,
Boolean shared, String tenantId, NetworkType networkType, String physicalNetworkName, Integer segmentationId,
Boolean shared, String tenantId, String availabilityZone, NetworkType networkType, String physicalNetworkName, Integer segmentationId,
Boolean external, Boolean portSecurity, String profileId, String multicastIp, String segmentAdd,
String segmentDel, String memberSegments, ImmutableSet<NetworkSegment> segments, String networkFlavor) {
// No checkNotNulls. With Neutron, any of these properties can be left null when used in an update.
@ -101,6 +103,7 @@ public class Network {
this.adminStateUp = adminStateUp;
this.shared = shared;
this.tenantId = tenantId;
this.availabilityZone = availabilityZone;
this.networkType = networkType;
this.physicalNetworkName = physicalNetworkName;
this.segmentationId = segmentationId;
@ -132,6 +135,7 @@ public class Network {
network.adminStateUp,
network.shared,
network.tenantId,
network.availabilityZone,
network.networkType,
network.physicalNetworkName,
network.segmentationId,
@ -205,6 +209,14 @@ public class Network {
return tenantId;
}
/**
* @return the availability zone of the Network
*/
@Nullable
public String getAvailabilityZone() {
return availabilityZone;
}
/**
* @return the networkType of the Network
*/
@ -304,7 +316,7 @@ public class Network {
@Override
public int hashCode() {
return Objects.hashCode(id, status, subnets, name, adminStateUp, shared, tenantId, networkType,
return Objects.hashCode(id, status, subnets, name, adminStateUp, shared, tenantId, availabilityZone, networkType,
physicalNetworkName, segmentationId, external, portSecurity, profileId, multicastIp, segmentAdd, segmentDel,
memberSegments, segments, networkFlavor);
}
@ -323,6 +335,7 @@ public class Network {
&& Objects.equal(this.adminStateUp, that.adminStateUp)
&& Objects.equal(this.shared, that.shared)
&& Objects.equal(this.tenantId, that.tenantId)
&& Objects.equal(this.availabilityZone, that.availabilityZone)
&& Objects.equal(this.networkType, that.networkType)
&& Objects.equal(this.physicalNetworkName, that.physicalNetworkName)
&& Objects.equal(this.segmentationId, that.segmentationId)
@ -347,6 +360,7 @@ public class Network {
.add("adminStateUp", adminStateUp)
.add("shared", shared)
.add("tenantId", tenantId)
.add("availabilityZone", availabilityZone)
.add("networkType", networkType)
.add("physicalNetworkName", physicalNetworkName)
.add("segmentationId", segmentationId)
@ -436,6 +450,17 @@ public class Network {
return self();
}
/**
* Provide the availabilityZone to the Network's Builder.
*
* @return the Builder.
* @see Network#getAvailabilityZone() ()
*/
public ParameterizedBuilderType availabilityZone(String availabilityZone) {
network.availabilityZone = availabilityZone;
return self();
}
/**
* Provide the networkType to the Network's Builder.
*

View File

@ -17,7 +17,9 @@
package org.jclouds.openstack.neutron.v2.domain;
import java.beans.ConstructorProperties;
import java.util.Set;
import com.google.common.base.Predicate;
import org.jclouds.openstack.v2_0.domain.Link;
import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
@ -33,4 +35,25 @@ public class Networks extends PaginatedCollection<Network> {
protected Networks(Iterable<Network> networks, Iterable<Link> networksLinks) {
super(networks, networksLinks);
}
public static class Predicates {
public static Predicate<Network> externalNetworks(final String availabilityZone) {
return new Predicate<Network>() {
@Override
public boolean apply(Network network) {
return availabilityZone.equals(network.getAvailabilityZone()) && network.getExternal();
}
};
}
public static Predicate<Network> namedNetworks(final Set<String> names) {
return new Predicate<Network>() {
@Override
public boolean apply(Network network) {
return names.contains(network.getName());
}
};
}
}
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.neutron.v2.extensions;
package org.jclouds.openstack.neutron.v2.features;
import javax.inject.Named;
import javax.ws.rs.Consumes;
@ -37,9 +37,7 @@ import org.jclouds.openstack.neutron.v2.domain.FloatingIPs;
import org.jclouds.openstack.neutron.v2.fallbacks.EmptyFloatingIPsFallback;
import org.jclouds.openstack.neutron.v2.functions.FloatingIPsToPagedIterable;
import org.jclouds.openstack.neutron.v2.functions.ParseFloatingIPs;
import org.jclouds.openstack.v2_0.ServiceType;
import org.jclouds.openstack.v2_0.options.PaginationOptions;
import org.jclouds.openstack.v2_0.services.Extension;
import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
@ -61,7 +59,6 @@ import com.google.common.annotations.Beta;
@Path("/floatingips")
@RequestFilters(AuthenticateRequest.class)
@Consumes(MediaType.APPLICATION_JSON)
@Extension(of = ServiceType.NETWORK, namespace = ExtensionNamespaces.L3_ROUTER, name = "Neutron L3 Router", alias = "router")
public interface FloatingIPApi {
/**

View File

@ -22,7 +22,7 @@ import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.internal.Arg0ToPagedIterable;
import org.jclouds.openstack.neutron.v2.NeutronApi;
import org.jclouds.openstack.neutron.v2.domain.FloatingIP;
import org.jclouds.openstack.neutron.v2.extensions.FloatingIPApi;
import org.jclouds.openstack.neutron.v2.features.FloatingIPApi;
import org.jclouds.openstack.v2_0.options.PaginationOptions;
import javax.inject.Inject;
@ -44,7 +44,7 @@ public class FloatingIPsToPagedIterable extends Arg0ToPagedIterable.FromCaller<F
@Override
protected Function<Object, IterableWithMarker<FloatingIP>> markerToNextForArg0(Optional<Object> arg0) {
String region = arg0.isPresent() ? arg0.get().toString() : null;
final FloatingIPApi floatingIPApi = api.getFloatingIPApi(region).get();
final FloatingIPApi floatingIPApi = api.getFloatingIPApi(region);
return new Function<Object, IterableWithMarker<FloatingIP>>() {
@SuppressWarnings("unchecked")

View File

@ -36,6 +36,7 @@ import org.jclouds.openstack.neutron.v2.domain.FloatingIP;
import org.jclouds.openstack.neutron.v2.domain.UpdateFirewall;
import org.jclouds.openstack.neutron.v2.domain.UpdateFirewallPolicy;
import org.jclouds.openstack.neutron.v2.domain.UpdateFirewallRule;
import org.jclouds.openstack.neutron.v2.features.FloatingIPApi;
import org.jclouds.openstack.neutron.v2.internal.BaseNeutronApiMockTest;
import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
import org.jclouds.openstack.v2_0.options.PaginationOptions;
@ -130,13 +131,12 @@ public class FWaaSApiMockTest extends BaseNeutronApiMockTest {
public void testListPagedFirewall() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(stringFromResource("/floatingip_list_response_paged1.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(stringFromResource("/floatingip_list_response_paged2.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
// Note: Lazy! Have to actually look at the collection.
List<FloatingIP> floatingIPs = api.list().concat().toList();
@ -144,9 +144,8 @@ public class FWaaSApiMockTest extends BaseNeutronApiMockTest {
/*
* Check request
*/
assertEquals(server.getRequestCount(), 4);
assertEquals(server.getRequestCount(), 3);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips?marker=71c1e68c-171a-4aa2-aca5-50ea153a3718");

View File

@ -15,28 +15,25 @@
* limitations under the License.
*/
package org.jclouds.openstack.neutron.v2.extensions;
package org.jclouds.openstack.neutron.v2.features;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.Set;
import com.google.common.base.Predicate;
import org.jclouds.openstack.neutron.v2.domain.FloatingIP;
import org.jclouds.openstack.neutron.v2.domain.IP;
import org.jclouds.openstack.neutron.v2.domain.Network;
import org.jclouds.openstack.neutron.v2.domain.NetworkType;
import org.jclouds.openstack.neutron.v2.domain.Subnet;
import org.jclouds.openstack.neutron.v2.features.NetworkApi;
import org.jclouds.openstack.neutron.v2.features.SubnetApi;
import org.jclouds.openstack.neutron.v2.internal.BaseNeutronApiLiveTest;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
/**
* Tests parsing and Guice wiring of RouterApi
* Tests parsing and Guice wiring of FloatingIPApi
*/
@Test(groups = "live", testName = "FloatingIPApiLiveTest")
public class FloatingIPApiLiveTest extends BaseNeutronApiLiveTest {
@ -44,45 +41,33 @@ public class FloatingIPApiLiveTest extends BaseNeutronApiLiveTest {
public void testCreateUpdateAndDeleteFloatingIP() {
for (String region : api.getConfiguredRegions()) {
SubnetApi subnetApi = api.getSubnetApi(region);
FloatingIPApi floatingIPApi = api.getFloatingIPApi(region).get();
FloatingIPApi floatingIPApi = api.getFloatingIPApi(region);
NetworkApi networkApi = api.getNetworkApi(region);
FloatingIP floatingIPGet = null;
String ipv4SubnetId = null;
Network network = null;
Network network;
try {
network = networkApi.create(
Network.createBuilder("jclouds-network-test").external(true).networkType(NetworkType.LOCAL).build());
assertNotNull(network);
network = networkApi.list().concat().firstMatch(new Predicate<Network>() {
@Override
public boolean apply(Network input) {
return input.getExternal();
}
}).orNull();
ipv4SubnetId = subnetApi.create(Subnet.createBuilder(network.getId(), "198.51.100.0/24").ipVersion(4)
.name("JClouds-Live-IPv4-Subnet").build()).getId();
floatingIPApi.create(FloatingIP.createBuilder(network.getId()).build());
if (network == null) Assert.fail("Cannot find a suitable external network. Please add it manually or contact your administrator");
FloatingIP floatingIP = floatingIPApi.create(FloatingIP.createBuilder(network.getId()).availabilityZone(network.getAvailabilityZone()).build());
/* List and Get test */
Set<FloatingIP> floatingIPs = floatingIPApi.list().concat().toSet();
FloatingIP floatingIPList = floatingIPs.iterator().next();
floatingIPGet = floatingIPApi.get(floatingIPList.getId());
floatingIPGet = floatingIPApi.get(floatingIP.getId());
assertNotNull(floatingIPGet);
assertEquals(floatingIPGet, floatingIPList);
assertTrue(floatingIPs.contains(floatingIP));
}
finally {
try {
assertTrue(floatingIPApi.delete(floatingIPGet.getId()));
}
finally {
try {
assertTrue(subnetApi.delete(ipv4SubnetId));
}
finally {
assertTrue(networkApi.delete(network.getId()));
}
}
}
}
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.neutron.v2.extensions;
package org.jclouds.openstack.neutron.v2.features;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@ -45,13 +45,12 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testCreateFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(
new MockResponse().setResponseCode(201).setBody(stringFromResource("/floatingip_create_response.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
FloatingIP.CreateFloatingIP createFip = FloatingIP.createBuilder("376da547-b977-4cfe-9cba-275c80debf57")
.portId("ce705c24-c1ef-408a-bda3-7bbd946164ab")
@ -62,9 +61,8 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "POST", uriApiVersion + "/floatingips", "/floatingip_create_request.json");
/*
@ -87,21 +85,19 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testListSpecificPageFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(stringFromResource("/floatingip_list_response_paged1.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
FloatingIPs floatingIPs = api.list(PaginationOptions.Builder.limit(2).marker("abcdefg"));
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips?limit=2&marker=abcdefg");
/*
@ -119,13 +115,12 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testListPagedFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(stringFromResource("/floatingip_list_response_paged1.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(stringFromResource("/floatingip_list_response_paged2.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
// Note: Lazy! Have to actually look at the collection.
List<FloatingIP> floatingIPs = api.list().concat().toList();
@ -133,9 +128,8 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
/*
* Check request
*/
assertEquals(server.getRequestCount(), 4);
assertEquals(server.getRequestCount(), 3);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips?marker=71c1e68c-171a-4aa2-aca5-50ea153a3718");
@ -154,22 +148,20 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testGetFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(
new MockResponse().setResponseCode(201).setBody(stringFromResource("/floatingip_get_response.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
FloatingIP floatingIP = api.get("12345");
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "GET", uriApiVersion + "/floatingips/12345");
/*
@ -192,13 +184,12 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testUpdateFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(
new MockResponse().setResponseCode(201).setBody(stringFromResource("/floatingip_update_response.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
FloatingIP.UpdateFloatingIP updateFloatingIP = FloatingIP.updateBuilder()
.portId("fc861431-0e6c-4842-a0ed-e2363f9bc3a8")
@ -209,9 +200,8 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "PUT", uriApiVersion + "/floatingips/12345", "/floatingip_update_request.json");
/*
@ -228,13 +218,12 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testUpdateFloatingIPDissociate() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(
new MockResponse().setResponseCode(201).setBody(stringFromResource("/floatingip_update_dissociate_response.json"))));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
FloatingIP.UpdateFloatingIP updateFloatingIP = FloatingIP.updateBuilder().build();
@ -243,9 +232,8 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "PUT", uriApiVersion + "/floatingips/12345", "/floatingip_update_dissociate_request.json");
/*
@ -262,22 +250,20 @@ public class FloatingIPApiMockTest extends BaseNeutronApiMockTest {
public void testDeleteFloatingIP() throws IOException, InterruptedException, URISyntaxException {
MockWebServer server = mockOpenStackServer();
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/extension_list.json"))));
server.enqueue(addCommonHeaders(
new MockResponse().setResponseCode(201)));
try {
NeutronApi neutronApi = api(server.getUrl("/").toString(), "openstack-neutron", overrides);
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne").get();
FloatingIPApi api = neutronApi.getFloatingIPApi("RegionOne");
boolean result = api.delete("12345");
/*
* Check request
*/
assertEquals(server.getRequestCount(), 3);
assertEquals(server.getRequestCount(), 2);
assertAuthentication(server);
assertExtensions(server, uriApiVersion + "");
assertRequest(server.takeRequest(), "DELETE", uriApiVersion + "/floatingips/12345");
/*

View File

@ -71,7 +71,7 @@ import org.jclouds.openstack.nova.v2_0.compute.loaders.FindSecurityGroupOrCreate
import org.jclouds.openstack.nova.v2_0.compute.loaders.LoadFloatingIpsForInstance;
import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
import org.jclouds.openstack.nova.v2_0.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.Server.Status;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.FlavorInRegion;
@ -137,7 +137,7 @@ public class NovaComputeServiceContextModule extends
bind(TemplateOptions.class).to(NovaTemplateOptions.class);
bind(new TypeLiteral<CacheLoader<RegionAndId, Iterable<? extends FloatingIP>>>() {
bind(new TypeLiteral<CacheLoader<RegionAndId, Iterable<? extends FloatingIpForServer>>>() {
}).annotatedWith(Names.named("FLOATINGIP")).to(LoadFloatingIpsForInstance.class);
bind(new TypeLiteral<Function<RegionSecurityGroupNameAndPorts, SecurityGroup>>() {
@ -214,8 +214,8 @@ public class NovaComputeServiceContextModule extends
@Provides
@Singleton
@Named("FLOATINGIP")
protected final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> instanceToFloatingIps(
@Named("FLOATINGIP") CacheLoader<RegionAndId, Iterable<? extends FloatingIP>> in) {
protected final LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> instanceToFloatingIps(
@Named("FLOATINGIP") CacheLoader<RegionAndId, Iterable<? extends FloatingIpForServer>> in) {
return CacheBuilder.newBuilder().build(in);
}
@ -313,7 +313,7 @@ public class NovaComputeServiceContextModule extends
@Override
public boolean apply(RegionAndId regionAndId) {
checkNotNull(regionAndId, "regionAndId");
checkNotNull(regionAndId, "serverId");
Server server = api.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
if (server == null) {
throw new IllegalStateException(String.format("Server %s not found.", regionAndId.getId()));
@ -333,7 +333,7 @@ public class NovaComputeServiceContextModule extends
@Override
public boolean apply(RegionAndId regionAndId) {
checkNotNull(regionAndId, "regionAndId");
checkNotNull(regionAndId, "serverId");
Server server = api.getServerApi(regionAndId.getRegion()).get(regionAndId.getId());
return server == null;
}

View File

@ -25,19 +25,29 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.collect.Sets;
import org.jclouds.Context;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.neutron.v2.NeutronApi;
import org.jclouds.openstack.neutron.v2.domain.Network;
import org.jclouds.openstack.neutron.v2.domain.Networks;
import org.jclouds.openstack.neutron.v2.domain.Port;
import org.jclouds.openstack.neutron.v2.features.NetworkApi;
import org.jclouds.openstack.neutron.v2.features.PortApi;
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.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
import org.jclouds.rest.ApiContext;
import org.jclouds.rest.InsufficientResourcesException;
import org.jclouds.rest.ResourceNotFoundException;
@ -45,30 +55,38 @@ import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* A function for adding and allocating an ip to a node
*/
public class AllocateAndAddFloatingIpToNode implements
Function<AtomicReference<NodeAndNovaTemplateOptions>, AtomicReference<NodeMetadata>> {
public class AllocateAndAddFloatingIpToNode
implements Function<AtomicReference<NodeAndNovaTemplateOptions>, AtomicReference<NodeMetadata>> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
@Inject(optional = true)
@Named("openstack-neutron")
private Supplier<Context> neutronContextSupplier;
private final Predicate<AtomicReference<NodeMetadata>> nodeRunning;
private final NovaApi novaApi;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache;
private final CleanupResources cleanupResources;
@Inject
public AllocateAndAddFloatingIpToNode(@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
NovaApi novaApi, @Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache, CleanupResources cleanupResources) {
public AllocateAndAddFloatingIpToNode(
@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning, NovaApi novaApi,
@Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache,
CleanupResources cleanupResources) {
this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
this.novaApi = checkNotNull(novaApi, "novaApi");
this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
@ -78,13 +96,45 @@ public class AllocateAndAddFloatingIpToNode implements
@Override
public AtomicReference<NodeMetadata> apply(AtomicReference<NodeAndNovaTemplateOptions> input) {
checkState(nodeRunning.apply(input.get().getNodeMetadata()), "node never achieved state running %s", input.get().getNodeMetadata());
NodeMetadata node = input.get().getNodeMetadata().get();
final NodeMetadata node = input.get().getNodeMetadata().get();
// node's location is a host
String regionId = node.getLocation().getParent().getId();
FloatingIPApi floatingIpApi = novaApi.getFloatingIPApi(regionId).get();
Optional<Set<String>> poolNames = input.get().getNovaTemplateOptions().get().getFloatingIpPoolNames();
Optional<FloatingIP> ip = allocateFloatingIPForNode(floatingIpApi, poolNames, node.getId());
String availabilityZone = getAvailabilityZoneFromTemplateOptionsOrDefault(input, regionId);
if (isNeutronLinked()) {
org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi = getFloatingIPApi(regionId);
final Optional<Port> optionalPort = getPortApi(regionId).list().concat().firstMatch(new Predicate<Port>() {
@Override
public boolean apply(@Nullable Port input) {
return input.getDeviceId().equals(node.getProviderId());
}
});
if (optionalPort.isPresent()) {
Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> floatingIPOptional = tryFindExistingFloatingIp(neutronFloatingApi, availabilityZone);
org.jclouds.openstack.neutron.v2.domain.FloatingIP floatingIP;
if (floatingIPOptional.isPresent()) {
floatingIP = floatingIPOptional.get();
} else {
floatingIP = createFloatingIpUsingNeutron(neutronFloatingApi, node, poolNames, availabilityZone);
}
org.jclouds.openstack.neutron.v2.domain.FloatingIP ip = neutronFloatingApi.update(floatingIP.getId(),
org.jclouds.openstack.neutron.v2.domain.FloatingIP.UpdateFloatingIP
.updateBuilder()
.portId(optionalPort.get().getId())
.build());
input.get().getNodeMetadata().set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.getFloatingIpAddress())).build());
} else {
logger.error("Node %s doesn't have a port to attach a floating IP", node);
throw new IllegalStateException("Missing required port in node: " + node);
}
} else { // try nova
FloatingIPApi floatingIpApi = novaApi.getFloatingIPApi(regionId).get();
Optional<FloatingIP> ip = allocateFloatingIPForNodeOnNova(floatingIpApi, poolNames, node.getId());
if (!ip.isPresent()) {
cleanupResources.apply(node);
throw new InsufficientResourcesException("Failed to allocate a FloatingIP for node(" + node.getId() + ")");
@ -92,23 +142,34 @@ public class AllocateAndAddFloatingIpToNode implements
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.asMap().putIfAbsent(RegionAndId.fromSlashEncoded(node.getId()), ImmutableList.of(ip.get()));
floatingIpCache.asMap().put(RegionAndId.fromSlashEncoded(node.getId()), ImmutableList.of(FloatingIpForServer.create(RegionAndId.fromSlashEncoded(node.getId()), ip.get().getId(), ip.get().getIp())));
}
return input.get().getNodeMetadata();
}
private String getAvailabilityZoneFromTemplateOptionsOrDefault(AtomicReference<NodeAndNovaTemplateOptions> input, String regionId) {
return MoreObjects.firstNonNull(input.get().getNovaTemplateOptions().get().getAvailabilityZone(),
Iterables.get(novaApi.getAvailabilityZoneApi(regionId).get().listAvailabilityZones(), 0).getName());
}
/**
* 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
* @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<FloatingIP>
*/
private synchronized Optional<FloatingIP> allocateFloatingIPForNode(FloatingIPApi floatingIpApi, Optional<Set<String>> poolNames, String nodeID) {
private synchronized Optional<FloatingIP> allocateFloatingIPForNodeOnNova(FloatingIPApi floatingIpApi,
Optional<Set<String>> poolNames, String nodeID) {
FloatingIP ip = null;
FloatingIP ip;
// 1.) Attempt to allocate from optionally passed poolNames
if (poolNames.isPresent()) {
@ -118,9 +179,11 @@ public class AllocateAndAddFloatingIpToNode implements
ip = floatingIpApi.allocateFromPool(poolName);
return Optional.of(ip);
} catch (ResourceNotFoundException ex) {
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ex.getMessage(), poolName, nodeID);
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ex.getMessage(),
poolName, nodeID);
} catch (InsufficientResourcesException ire) {
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ire.getMessage(), poolName, nodeID);
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ire.getMessage(),
poolName, nodeID);
}
}
}
@ -136,10 +199,11 @@ public class AllocateAndAddFloatingIpToNode implements
logger.trace("<< [%s] failed to create floating IP for node(%s)", ire.getMessage(), nodeID);
}
// 3.) If no IP was found make final attempt by searching through list of available IP's
// 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<FloatingIP> unassignedIps = Lists.newArrayList(Iterables.filter(floatingIpApi.list(),
new Predicate<FloatingIP>() {
List<FloatingIP> unassignedIps = Lists
.newArrayList(Iterables.filter(floatingIpApi.list(), new Predicate<FloatingIP>() {
@Override
public boolean apply(FloatingIP arg0) {
@ -156,8 +220,72 @@ public class AllocateAndAddFloatingIpToNode implements
return Optional.fromNullable(ip);
}
private Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> tryFindExistingFloatingIp(org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi, final String availabilityZone) {
Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> floatingIPOptional = neutronFloatingApi.list().concat().firstMatch(new Predicate<org.jclouds.openstack.neutron.v2.domain.FloatingIP>() {
@Override
public boolean apply(@Nullable org.jclouds.openstack.neutron.v2.domain.FloatingIP input) {
return input.getPortId() == null && input.getAvailabilityZone().equals(availabilityZone);
}
});
return floatingIPOptional;
}
private org.jclouds.openstack.neutron.v2.domain.FloatingIP createFloatingIpUsingNeutron(org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi,
NodeMetadata node, Optional<Set<String>> poolNames, final String availabilityZone) {
String regionId = node.getLocation().getParent().getId();
List<Network> networks = getSuitableNetworks(regionId, availabilityZone, poolNames.or(Sets.<String>newHashSet()));
org.jclouds.openstack.neutron.v2.domain.FloatingIP floatingIP = null;
for (Network network : networks) {
try {
logger.debug(">> allocating floating IP from network %s for node(%s)", network, node);
org.jclouds.openstack.neutron.v2.domain.FloatingIP createFloatingIP = org.jclouds.openstack.neutron.v2.domain.FloatingIP.CreateFloatingIP.createBuilder(network.getId()).availabilityZone(network.getAvailabilityZone()).build();
floatingIP = neutronFloatingApi.create((org.jclouds.openstack.neutron.v2.domain.FloatingIP.CreateFloatingIP) createFloatingIP);
logger.debug(">> allocated floating IP(%s) from network(%s) for node(%s)", floatingIP, network, node);
floatingIpCache.asMap().put(RegionAndId.fromSlashEncoded(node.getId()), ImmutableList.of(FloatingIpForServer.create(RegionAndId.fromSlashEncoded(node.getId()), floatingIP.getId(), floatingIP.getFloatingIpAddress())));
return floatingIP;
} catch (Exception ex) {
logger.trace("<< [%s] failed to allocate a floating IP from network %s for node(%s)", ex.getMessage(), network, node);
}
}
throw new IllegalStateException("Failed to allocate a floating IP for node " + node + ".\n" +
"Failed to find suitable external networks or to allocate from poolNames specified: "
+ Iterables.toString(poolNames.get()));
}
/**
* Get all suitable networks to allocate a floating ip
*
* It will prefer networks specified using the poolNames first and then the external networks in the given availability zone
*/
private List<Network> getSuitableNetworks(String regionId, final String availabilityZone, final Set<String> poolNames) {
List<Network> allNetworks = getNetworkApi(regionId).list().concat().toList();
Iterable<Network> externalNetworks = Iterables.filter(allNetworks, Networks.Predicates.externalNetworks(availabilityZone));
Iterable<Network> networksFromPoolName = Iterables.filter(allNetworks, Networks.Predicates.namedNetworks(poolNames));
return Lists.newArrayList(Iterables.concat(networksFromPoolName, externalNetworks));
}
private boolean isNeutronLinked() {
return neutronContextSupplier != null && neutronContextSupplier.get() != null;
}
private org.jclouds.openstack.neutron.v2.features.FloatingIPApi getFloatingIPApi(String region) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getFloatingIPApi(region);
}
private PortApi getPortApi(String regionId) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getPortApi(regionId);
}
private NetworkApi getNetworkApi(String regionId) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getNetworkApi(regionId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper("AllocateAndAddFloatingIpToNode").toString();
}
}

View File

@ -68,6 +68,7 @@ public class CleanupResources implements Function<NodeMetadata, Boolean> {
public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(Set<String> tags) {
String securityGroupIdCreatedByJclouds = getSecurityGroupIdCreatedByJclouds(tags);
if (securityGroupIdCreatedByJclouds == null) return true;
return securityGroupExtension.removeSecurityGroup(securityGroupIdCreatedByJclouds);
}

View File

@ -19,19 +19,23 @@ package org.jclouds.openstack.nova.v2_0.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.base.Supplier;
import com.google.inject.Inject;
import org.jclouds.Context;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.neutron.v2.NeutronApi;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.cache.LoadingCache;
import org.jclouds.rest.ApiContext;
/**
* A function for removing and deallocating an ip address from a node
@ -42,31 +46,51 @@ public class RemoveFloatingIpFromNodeAndDeallocate implements Function<RegionAnd
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
@Inject(optional = true)
@Named("openstack-neutron")
private Supplier<Context> neutronContextSupplier;
private final NovaApi novaApi;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache;
@Inject
public RemoveFloatingIpFromNodeAndDeallocate(NovaApi novaApi,
@Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIP>> floatingIpCache) {
@Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache) {
this.novaApi = checkNotNull(novaApi, "novaApi");
this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
}
@Override
public RegionAndId apply(RegionAndId id) {
if (isNeutronLinked()) {
for (FloatingIpForServer floatingIpForServer : floatingIpCache.getUnchecked(id)) {
logger.debug(">> deallocating floatingIp(%s)", floatingIpForServer);
getFloatingIPApi(id.getRegion()).delete(floatingIpForServer.floatingIpId());
}
} else { // try nova
FloatingIPApi floatingIpApi = novaApi.getFloatingIPApi(id.getRegion()).get();
for (FloatingIP ip : floatingIpCache.getUnchecked(id)) {
logger.debug(">> removing floatingIp(%s) from node(%s)", ip, id);
floatingIpApi.removeFromServer(ip.getIp(), id.getId());
logger.debug(">> deallocating floatingIp(%s)", ip);
floatingIpApi.delete(ip.getId());
for (FloatingIpForServer floatingIpForServer : floatingIpCache.getUnchecked(id)) {
logger.debug(">> removing floatingIp(%s) from node(%s)", floatingIpForServer, id);
floatingIpApi.removeFromServer(floatingIpForServer.ip(), id.getId());
logger.debug(">> deallocating floatingIp(%s)", floatingIpForServer);
floatingIpApi.delete(floatingIpForServer.floatingIpId());
}
}
floatingIpCache.invalidate(id);
return id;
}
// FIXME remove duplications from AllocateAndAddFloatingIpToNode
private boolean isNeutronLinked() {
return neutronContextSupplier != null && neutronContextSupplier.get() != null;
}
private org.jclouds.openstack.neutron.v2.features.FloatingIPApi getFloatingIPApi(String region) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getFloatingIPApi(region);
}
@Override
public String toString() {
return MoreObjects.toStringHelper("RemoveFloatingIpFromNodeAndDecreate").toString();
return MoreObjects.toStringHelper("RemoveFloatingIpFromNodeAndDeallocate").toString();
}
}

View File

@ -19,8 +19,10 @@ package org.jclouds.openstack.nova.v2_0.compute.loaders;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.base.Function;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
@ -35,7 +37,7 @@ import com.google.common.collect.ImmutableSet;
* them.
*/
@Singleton
public class LoadFloatingIpsForInstance extends CacheLoader<RegionAndId, Iterable<? extends FloatingIP>> {
public class LoadFloatingIpsForInstance extends CacheLoader<RegionAndId, Iterable<? extends FloatingIpForServer>> {
private final NovaApi api;
@Inject
@ -44,7 +46,7 @@ public class LoadFloatingIpsForInstance extends CacheLoader<RegionAndId, Iterabl
}
@Override
public Iterable<? extends FloatingIP> load(final RegionAndId key) throws Exception {
public Iterable<? extends FloatingIpForServer> load(final RegionAndId key) throws Exception {
String region = key.getRegion();
Optional<? extends FloatingIPApi> ipApiOptional = api.getFloatingIPApi(region);
if (ipApiOptional.isPresent()) {
@ -54,6 +56,12 @@ public class LoadFloatingIpsForInstance extends CacheLoader<RegionAndId, Iterabl
public boolean apply(FloatingIP input) {
return key.getId().equals(input.getInstanceId());
}
})
.transform(new Function<FloatingIP, FloatingIpForServer>() {
@Override
public FloatingIpForServer apply(FloatingIP input) {
return FloatingIpForServer.create(key, input.getId(), input.getIp());
}
});
}
return ImmutableSet.of();

View File

@ -0,0 +1,38 @@
/*
* 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.domain;
import com.google.auto.value.AutoValue;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
@AutoValue
public abstract class FloatingIpForServer {
public abstract RegionAndId serverId();
public abstract String floatingIpId();
public abstract String ip();
public static FloatingIpForServer create(RegionAndId serverId,
String floatingIpId,
String ip
) {
return new AutoValue_FloatingIpForServer(serverId, floatingIpId, ip);
}
FloatingIpForServer() {
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.domain;
import com.google.auto.value.AutoValue;
import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import org.jclouds.json.SerializedNames;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
@AutoValue
public abstract class PortInterface {
public enum State {
PROVISIONING, QUEUED, ACTIVE;
public static State fromValue(String value) {
Optional<State> state = Enums.getIfPresent(State.class, value.toUpperCase());
checkArgument(state.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(State.values()), value);
return state.get();
}
}
public abstract String portId();
public abstract String netId();
public abstract State portState();
public abstract List<FixedIP> fixedIPS();
@SerializedNames({"port_id", "net_id", "port_state", "fixed_ips"})
public static PortInterface create(String portId, String netId, State portState, List<FixedIP> fixedIPS) {
return new AutoValue_PortInterface(portId, netId, portState,
fixedIPS == null ? ImmutableList.<FixedIP> of() : ImmutableList.copyOf(fixedIPS)
);
}
PortInterface() {}
}

View File

@ -42,6 +42,7 @@ import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
import org.jclouds.collect.PagedIterable;
import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.openstack.nova.v2_0.domain.PortInterface;
import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
import org.jclouds.openstack.keystone.auth.filters.AuthenticateRequest;
@ -446,4 +447,18 @@ public interface ServerApi {
@Fallback(Fallbacks.EmptySetOnNotFoundOr404.class)
Set<SecurityGroup> listSecurityGroupForServer(@PathParam("id") String id);
/**
* Lists port interfaces that are attached to a server.
*
* @param id
* id of the server
* @return a list of ports attached to the server
*/
@Named("server:getPortInterfaces")
@GET
@Path("/{id}/os-interface")
@SelectJson("interfaceAttachments")
@Fallback(Fallbacks.EmptySetOnNotFoundOr404.class)
Set<PortInterface> listPortInterfaces(@PathParam("id") String id);
}

View File

@ -21,15 +21,12 @@ import static java.util.logging.Logger.getAnonymousLogger;
import java.util.Properties;
import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
import org.jclouds.logging.config.LoggingModule;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
import org.jclouds.openstack.keystone.config.KeystoneProperties;
import org.jclouds.openstack.nova.v2_0.config.NovaProperties;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
@Test(groups = "live", singleThreaded = true, testName = "NovaComputeServiceLiveTest")
@ -44,16 +41,6 @@ public class NovaComputeServiceLiveTest extends BaseComputeServiceLiveTest {
return new SshjSshClientModule();
}
@Override
protected LoggingModule getLoggingModule() {
return new SLF4JLoggingModule();
}
@Override
protected Iterable<Module> setupModules() {
return ImmutableSet.of(getLoggingModule(), credentialStoreModule, getSshModule());
}
@Override
public void testOptionToNotBlock() {
// start call is blocking anyway.
@ -62,14 +49,12 @@ public class NovaComputeServiceLiveTest extends BaseComputeServiceLiveTest {
@Test(enabled = true, dependsOnMethods = "testReboot")
public void testSuspendResume() throws Exception {
try {
// may fail because of lack of AdminActions extension or non-admin user, so log
// and continue
// may fail because of lack of AdminActions extension or non-admin user, so log and continue
super.testSuspendResume();
} catch (AuthorizationException e) {
getAnonymousLogger().info("testSuspendResume() threw, probably due to lack of privileges: " + e.getMessage());
} catch (UnsupportedOperationException e) {
getAnonymousLogger().info(
"testSuspendResume() threw, probably due to unavailable AdminActions extension: " + e.getMessage());
getAnonymousLogger().info("testSuspendResume() threw, probably due to unavailable AdminActions extension: " + e.getMessage());
}
}

View File

@ -0,0 +1,130 @@
/*
* 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;
import static java.util.logging.Logger.getAnonymousLogger;
import java.util.Properties;
import com.google.common.collect.ImmutableSet;
import org.jclouds.Context;
import org.jclouds.ContextBuilder;
import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
import org.jclouds.config.ContextLinking;
import org.jclouds.encryption.bouncycastle.config.BouncyCastleCryptoModule;
import org.jclouds.logging.config.LoggingModule;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
import org.jclouds.openstack.keystone.config.KeystoneProperties;
import org.jclouds.openstack.nova.v2_0.config.NovaProperties;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.testng.annotations.Test;
import com.google.inject.Module;
@Test(groups = "live", singleThreaded = true, testName = "NovaWithNeutronComputeServiceLiveTest")
public class NovaWithNeutronComputeServiceLiveTest extends BaseComputeServiceLiveTest {
private Context neutronApiContext;
public NovaWithNeutronComputeServiceLiveTest() {
provider = "openstack-nova";
Properties overrides = setupProperties();
neutronApiContext = ContextBuilder.newBuilder("openstack-neutron")
.endpoint(setIfTestSystemPropertyPresent(overrides,
"openstack-nova.endpoint"))
.credentials(setIfTestSystemPropertyPresent(overrides,
"openstack-nova.identity"),
setIfTestSystemPropertyPresent(overrides, "openstack-nova.credential"))
.modules(ImmutableSet.<Module>of(
new SshjSshClientModule(),
new SLF4JLoggingModule(),
new BouncyCastleCryptoModule())
)
.build();
}
@Override
protected Module getSshModule() {
return new SshjSshClientModule();
}
@Override
protected LoggingModule getLoggingModule() {
return new SLF4JLoggingModule();
}
@Override
protected Iterable<Module> setupModules() {
return ImmutableSet.of(
ContextLinking.linkContext(neutronApiContext),
getLoggingModule(),
credentialStoreModule,
getSshModule()
);
}
@Override
public void testOptionToNotBlock() {
// start call is blocking anyway.
}
@Test(enabled = true, dependsOnMethods = "testReboot")
public void testSuspendResume() throws Exception {
try {
// may fail because of lack of AdminActions extension or non-admin user, so log and continue
super.testSuspendResume();
} catch (AuthorizationException e) {
getAnonymousLogger().info("testSuspendResume() threw, probably due to lack of privileges: " + e.getMessage());
} catch (UnsupportedOperationException e) {
getAnonymousLogger().info("testSuspendResume() threw, probably due to unavailable AdminActions extension: " + e.getMessage());
}
}
@Test(enabled = true, dependsOnMethods = "testSuspendResume")
@Override
public void testGetNodesWithDetails() throws Exception {
super.testGetNodesWithDetails();
}
@Test(enabled = true, dependsOnMethods = "testSuspendResume")
@Override
public void testListNodes() throws Exception {
super.testListNodes();
}
@Test(enabled = true, dependsOnMethods = "testSuspendResume")
@Override
public void testListNodesByIds() throws Exception {
super.testListNodesByIds();
}
@Test(enabled = true, dependsOnMethods = { "testListNodes", "testGetNodesWithDetails", "testListNodesByIds" })
@Override
public void testDestroyNodes() {
super.testDestroyNodes();
}
@Override
protected Properties setupProperties() {
Properties props = super.setupProperties();
setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE);
setIfTestSystemPropertyPresent(props, NovaProperties.AUTO_ALLOCATE_FLOATING_IPS);
return props;
}
}

View File

@ -68,13 +68,26 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer
HttpResponse createFloatingIPResponse = HttpResponse.builder().statusCode(200).payload(
payloadFromResource("/floatingip_details.json")).build();
HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
.put("X-Auth-Token", authToken)
.build())
.build();
HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
payloadFromResource("/availability_zone_list.json")).build();
HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.3");
AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
ImmutableMap.<HttpRequest, HttpResponse> builder().put(keystoneAuthWithUsernameAndPasswordAndTenantName,
responseWithKeystoneAccess).put(extensionsOfNovaRequest, extensionsOfNovaResponse).put(
createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse).build()).getContext().utils().injector()
ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
.put(listAZs, listAZsResponseForUnassigned)
.put(extensionsOfNovaRequest, extensionsOfNovaResponse)
.put(createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse).build())
.getContext().utils().injector()
.getInstance(AllocateAndAddFloatingIpToNode.class);
AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);
@ -110,6 +123,16 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer
"{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 400}}",
"application/json")).build();
HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
.put("X-Auth-Token", authToken)
.build())
.build();
HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
payloadFromResource("/availability_zone_list.json")).build();
HttpRequest list = HttpRequest.builder().method("GET").endpoint(
URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-floating-ips")).headers(
ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
@ -121,11 +144,15 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer
HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.5");
AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
ImmutableMap.<HttpRequest, HttpResponse> builder().put(keystoneAuthWithUsernameAndPasswordAndTenantName,
responseWithKeystoneAccess).put(extensionsOfNovaRequest, extensionsOfNovaResponse).put(
createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse).put(list,
listResponseForUnassigned).build()).getContext().utils().injector()
ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
.put(listAZs, listAZsResponseForUnassigned)
.put(extensionsOfNovaRequest, extensionsOfNovaResponse)
.put(createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse)
.put(list, listResponseForUnassigned)
.build())
.getContext().utils().injector()
.getInstance(AllocateAndAddFloatingIpToNode.class);
AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);
@ -148,6 +175,16 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer
"{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 404}}",
"application/json")).build();
HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
.put("X-Auth-Token", authToken)
.build())
.build();
HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
payloadFromResource("/availability_zone_list.json")).build();
HttpRequest list = HttpRequest.builder().method("GET").endpoint(
URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-floating-ips")).headers(
ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
@ -159,11 +196,14 @@ public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeSer
HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.5");
AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
ImmutableMap.<HttpRequest, HttpResponse> builder().put(keystoneAuthWithUsernameAndPasswordAndTenantName,
responseWithKeystoneAccess).put(extensionsOfNovaRequest, extensionsOfNovaResponse).put(
createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse).put(list,
listResponseForUnassigned).build()).getContext().utils().injector()
ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
.put(listAZs, listAZsResponseForUnassigned)
.put(extensionsOfNovaRequest, extensionsOfNovaResponse)
.put(createFloatingIP, createFloatingIPResponse)
.put(addFloatingIPRequest, addFloatingIPResponse)
.put(list, listResponseForUnassigned).build())
.getContext().utils().injector()
.getInstance(AllocateAndAddFloatingIpToNode.class);
AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);

View File

@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertFalse;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
import org.testng.annotations.Test;
@ -50,8 +51,8 @@ public class LoadFloatingIpsForInstanceTest {
replay(ipApi);
LoadFloatingIpsForInstance parser = new LoadFloatingIpsForInstance(api);
assertEquals(ImmutableSet.copyOf(parser.load(RegionAndId.fromRegionAndId("RegionOne", "i-blah"))), ImmutableSet.of(testIp));
FloatingIpForServer floatingIpForServer = FloatingIpForServer.create(RegionAndId.fromRegionAndId("RegionOne", "i-blah"), "1", "1.1.1.1");
assertEquals(ImmutableSet.copyOf(parser.load(RegionAndId.fromRegionAndId("RegionOne", "i-blah"))), ImmutableSet.of(floatingIpForServer));
verify(api);
verify(ipApi);

View File

@ -0,0 +1,18 @@
{
"availabilityZoneInfo": [
{
"zoneState": {
"available": true
},
"hosts": null,
"zoneName": "uk-1a"
},
{
"zoneState": {
"available": true
},
"hosts": null,
"zoneName": "uk-1b"
}
]
}

View File

@ -270,7 +270,6 @@ public abstract class Aws4SignerBase {
}
}
/**
* hash string (encoding UTF_8) with sha256
*