From e5b45beb4c66df16d137660cdea559c26543270d Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Thu, 3 May 2012 14:18:42 +0100 Subject: [PATCH 1/7] openstack-nova: ensuring we use the smallest available flavor when creating servers in live tests (else devstack fails, badly) --- .../v1_1/internal/BaseNovaClientLiveTest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java index 2a37f52f7e..99151fefaa 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java @@ -18,13 +18,18 @@ */ package org.jclouds.openstack.nova.v1_1.internal; +import static org.jclouds.compute.util.ComputeServiceUtils.getCores; +import static org.jclouds.compute.util.ComputeServiceUtils.getSpace; + import java.util.Properties; +import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest; import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; import org.jclouds.openstack.nova.v1_1.NovaAsyncClient; import org.jclouds.openstack.nova.v1_1.NovaClient; import org.jclouds.openstack.nova.v1_1.config.NovaProperties; +import org.jclouds.openstack.nova.v1_1.domain.Flavor; import org.jclouds.openstack.nova.v1_1.domain.Server; import org.jclouds.openstack.nova.v1_1.domain.Server.Status; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; @@ -36,7 +41,9 @@ import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; import com.google.common.base.Throwables; +import com.google.common.collect.ComparisonChain; import com.google.common.collect.Iterables; +import com.google.common.collect.Ordering; /** * Tests behavior of {@code NovaClient} @@ -100,7 +107,13 @@ public class BaseNovaClientLiveTest extends BaseComputeServiceContextLiveTest { protected String flavorRefForZone(String zoneId) { FlavorClient flavorClient = novaContext.getApi().getFlavorClientForZone(zoneId); - return Iterables.getLast(flavorClient.listFlavors()).getId(); + return DEFAULT_FLAVOR_ORDERING.min(flavorClient.listFlavorsInDetail()).getId(); } + static final Ordering DEFAULT_FLAVOR_ORDERING = new Ordering() { + public int compare(Flavor left, Flavor right) { + return ComparisonChain.start().compare(left.getVcpus(), right.getVcpus()).compare(left.getRam(), right.getRam()) + .compare(left.getDisk(), right.getDisk()).result(); + } + }; } From 42df3d339c40c7d13e004610bd43a4b45960d8c6 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Thu, 3 May 2012 14:24:47 +0100 Subject: [PATCH 2/7] openstack-nova: Adding Virtual Interface extension --- .../openstack/nova/v1_1/NovaAsyncClient.java | 8 ++ .../openstack/nova/v1_1/NovaClient.java | 8 ++ .../v1_1/config/NovaRestClientModule.java | 1 + .../nova/v1_1/domain/VirtualInterface.java | 129 ++++++++++++++++++ .../VirtualInterfaceAsyncClient.java | 60 ++++++++ .../extensions/VirtualInterfaceClient.java | 46 +++++++ ...paceEqualsAnyNamespaceInExtensionsSet.java | 2 + .../VirtualInterfaceClientExpectTest.java | 65 +++++++++ .../VirtualInterfaceClientLiveTest.java | 70 ++++++++++ .../resources/virtual_interfaces_list.json | 1 + 10 files changed, 390 insertions(+) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/VirtualInterface.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceAsyncClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClient.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientExpectTest.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientLiveTest.java create mode 100644 apis/openstack-nova/src/test/resources/virtual_interfaces_list.json diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index 9f5d32b035..d00d5cce54 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -28,6 +28,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageAsyncClient; +import org.jclouds.openstack.nova.v1_1.extensions.VirtualInterfaceAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; @@ -127,4 +128,11 @@ public interface NovaAsyncClient { @Delegate Optional getVolumeExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to Virtual Interface features. + */ + @Delegate + Optional getVirtualInterfaceExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index 37dc81e228..866351c539 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -30,6 +30,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageClient; +import org.jclouds.openstack.nova.v1_1.extensions.VirtualInterfaceClient; import org.jclouds.openstack.nova.v1_1.extensions.VolumeClient; import org.jclouds.openstack.nova.v1_1.features.ExtensionClient; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; @@ -131,4 +132,11 @@ public interface NovaClient { Optional getVolumeExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides synchronous access to Virtual Interface features. + */ + @Delegate + Optional getVirtualInterfaceExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index 8a9a6e20bc..f2956abe61 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -74,6 +74,7 @@ public class NovaRestClientModule extends RestClientModule builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromVirtualInterface(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String macAddress; + + /** + * @see VirtualInterface#getId() + */ + public T id(String id) { + this.id = id; + return self(); + } + + /** + * @see VirtualInterface#getMacAddress() + */ + public T macAddress(String macAddress) { + this.macAddress = macAddress; + return self(); + } + + public VirtualInterface build() { + return new VirtualInterface(this); + } + + public T fromVirtualInterface(VirtualInterface in) { + return this + .id(in.getId()) + .macAddress(in.getMacAddress()) + ; + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + private final String id; + @SerializedName(value="mac_address") + private final String macAddress; + + protected VirtualInterface(Builder builder) { + this.id = checkNotNull(builder.id, "id"); + this.macAddress = checkNotNull(builder.macAddress, "macAddress"); + } + + public String getId() { + return this.id; + } + + public String getMacAddress() { + return this.macAddress; + } + + @Override + public int hashCode() { + return Objects.hashCode(id, macAddress); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + VirtualInterface that = VirtualInterface.class.cast(obj); + return Objects.equal(this.id, that.id) + && Objects.equal(this.macAddress, that.macAddress) + ; + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", id) + .add("macAddress", macAddress) + ; + } + + @Override + public String toString() { + return string().toString(); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceAsyncClient.java new file mode 100644 index 0000000000..4cd07f6737 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceAsyncClient.java @@ -0,0 +1,60 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.openstack.nova.v1_1.extensions; + +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.filters.AuthenticateRequest; +import org.jclouds.openstack.nova.v1_1.domain.VirtualInterface; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides asynchronous access to Virtual Interface features (VIFs). + * + * @see VirtualInterfaceClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VIRTUAL_INTERFACES) +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface VirtualInterfaceAsyncClient { + /** + * @see VirtualInterfaceClient#listVirtualInterfacesForServer(String) + */ + @GET + @SelectJson("virtual_interfaces") + @Consumes(MediaType.APPLICATION_JSON) + @Path("/servers/{server_id}/os-virtual-interfaces") + @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class) + ListenableFuture> listVirtualInterfacesForServer(@PathParam("server_id") String serverId); +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClient.java new file mode 100644 index 0000000000..acf7692228 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClient.java @@ -0,0 +1,46 @@ +/** + * 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.v1_1.extensions; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.nova.v1_1.domain.VirtualInterface; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; + +/** + * Provides synchronous access to Virtual Interface features (VIFs). + * + * @see VirtualInterfaceAsyncClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.VIRTUAL_INTERFACES) +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface VirtualInterfaceClient { + + /** + * Returns the list of Virtual Interfaces for a given instance. + * + * @return the list of snapshots + */ + Set listVirtualInterfacesForServer(String serverId); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java index 85406b5e4c..b55e0830f2 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java @@ -68,6 +68,8 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio URI.create("http://docs.openstack.org/compute/ext/hosts/api/v1.1")) .put(URI.create(ExtensionNamespaces.VOLUMES), URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1")) + .put(URI.create(ExtensionNamespaces.VIRTUAL_INTERFACES), + URI.create("http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1")) .build(); @Inject diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientExpectTest.java new file mode 100644 index 0000000000..553be0dac1 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientExpectTest.java @@ -0,0 +1,65 @@ +/** + * 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.v1_1.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.net.URI; + +import org.jclouds.openstack.nova.v1_1.domain.VirtualInterface; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +/** + * Tests parsing and guice wiring of VirtualInterfaceClient + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "VirtualInterfaceClientLiveTest") +public class VirtualInterfaceClientExpectTest extends BaseNovaClientExpectTest { + + public void testListVirtualInterfaces() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/os-virtual-interfaces"); + VirtualInterfaceClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/virtual_interfaces_list.json")).build() + ).getVirtualInterfaceExtensionForZone("az-1.region-a.geo-1").get(); + + VirtualInterface vif = Iterables.getOnlyElement(client.listVirtualInterfacesForServer("1")); + assertEquals(vif.getId(), "02315827-b05c-4668-9c05-75c68838074a"); + assertEquals(vif.getMacAddress(), "fa:16:3e:09:71:34"); + } + + public void testListVirtualInterfacesFailNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/os-virtual-interfaces"); + VirtualInterfaceClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getVirtualInterfaceExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.listVirtualInterfacesForServer("1").isEmpty()); + } +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientLiveTest.java new file mode 100644 index 0000000000..9190502fe9 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/VirtualInterfaceClientLiveTest.java @@ -0,0 +1,70 @@ +/** + * 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.v1_1.extensions; + +import static org.testng.Assert.assertNotNull; + +import java.util.Set; + +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.VirtualInterface; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of VirtualInterfaceClient + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "VirtualInterfaceClientLiveTest", singleThreaded = true) +public class VirtualInterfaceClientLiveTest extends BaseNovaClientLiveTest { + private Optional clientOption; + private String zone; + + + @BeforeClass(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); + clientOption = novaContext.getApi().getVirtualInterfaceExtensionForZone(zone); + } + + public void testListVirtualInterfaces() { + if (clientOption.isPresent()) { + Server testServer = null; + try { + testServer = createServerInZone(zone); + Set results = clientOption.get().listVirtualInterfacesForServer(testServer.getId()); + for (VirtualInterface vif : results) { + assertNotNull(vif.getId()); + assertNotNull(vif.getMacAddress()); + } + } finally { + if (testServer != null) { + novaContext.getApi().getServerClientForZone(zone).deleteServer(testServer.getId()); + } + } + } +} +} diff --git a/apis/openstack-nova/src/test/resources/virtual_interfaces_list.json b/apis/openstack-nova/src/test/resources/virtual_interfaces_list.json new file mode 100644 index 0000000000..7df0182f03 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/virtual_interfaces_list.json @@ -0,0 +1 @@ +{"virtual_interfaces": [{"id": "02315827-b05c-4668-9c05-75c68838074a", "mac_address": "fa:16:3e:09:71:34"}]} \ No newline at end of file From 06d3ef02badc1aaab93cc1820fe7d703e8e0d9e4 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Fri, 4 May 2012 11:02:21 +0100 Subject: [PATCH 3/7] openstack-nova: Adding CREATESERVEREXT extension (renamed ServerWithSecurityGroupsClient to be clear about what's on offer) --- .../openstack/nova/v1_1/NovaAsyncClient.java | 10 + .../openstack/nova/v1_1/NovaClient.java | 9 + .../nova/v1_1/config/NovaParserModule.java | 29 +- .../v1_1/config/NovaRestClientModule.java | 1 + .../openstack/nova/v1_1/domain/Server.java | 35 ++- .../v1_1/domain/ServerWithSecurityGroups.java | 271 ++++++++++++++++++ .../ServerWithSecurityGroupsAsyncClient.java | 61 ++++ .../ServerWithSecurityGroupsClient.java | 49 ++++ ...paceEqualsAnyNamespaceInExtensionsSet.java | 4 +- ...verWithSecurityGroupsClientExpectTest.java | 64 +++++ ...erverWithSecurityGroupsClientLiveTest.java | 82 ++++++ .../server_with_security_groups.json | 1 + 12 files changed, 596 insertions(+), 20 deletions(-) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java create mode 100644 apis/openstack-nova/src/test/resources/server_with_security_groups.json diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index d00d5cce54..da1c3e16a9 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -27,6 +27,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; +import org.jclouds.openstack.nova.v1_1.extensions.ServerWithSecurityGroupsAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.VirtualInterfaceAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.VolumeAsyncClient; @@ -135,4 +136,13 @@ public interface NovaAsyncClient { @Delegate Optional getVirtualInterfaceExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + + /** + * Provides asynchronous access to Server Extra Data features. + */ + @Delegate + Optional getServerExtraDataExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index 866351c539..3f1cbe269a 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -29,6 +29,7 @@ import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; +import org.jclouds.openstack.nova.v1_1.extensions.ServerWithSecurityGroupsClient; import org.jclouds.openstack.nova.v1_1.extensions.SimpleTenantUsageClient; import org.jclouds.openstack.nova.v1_1.extensions.VirtualInterfaceClient; import org.jclouds.openstack.nova.v1_1.extensions.VolumeClient; @@ -139,4 +140,12 @@ public interface NovaClient { Optional getVirtualInterfaceExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides synchronous access to Server Extra Data features. + */ + @Delegate + Optional getServerExtraDataExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java index dbe79c90a0..3911554c21 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java @@ -20,14 +20,19 @@ package org.jclouds.openstack.nova.v1_1.config; import java.lang.reflect.Type; import java.util.Map; +import java.util.Set; import javax.inject.Singleton; import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.openstack.nova.v1_1.domain.HostResourceUsage; +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.ServerWithSecurityGroups; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; @@ -39,13 +44,17 @@ import com.google.inject.Provides; /** * @author Adrian Cole + * @author Adam Lowe */ public class NovaParserModule extends AbstractModule { @Provides @Singleton public Map provideCustomAdapterBindings() { - return ImmutableMap. of(HostResourceUsage.class, new HostResourceUsageAdapter()); + return ImmutableMap. of( + HostResourceUsage.class, new HostResourceUsageAdapter(), + ServerWithSecurityGroups.class, new ServerWithSecurityGroupsAdapter() + ); } @Override @@ -79,4 +88,22 @@ public class NovaParserModule extends AbstractModule { } } + @Singleton + public static class ServerWithSecurityGroupsAdapter implements JsonDeserializer { + @Override + public ServerWithSecurityGroups deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) + throws JsonParseException { + Server server = context.deserialize(jsonElement, Server.class); + ServerWithSecurityGroups.Builder result = ServerWithSecurityGroups.builder().fromServer(server); + Set names = Sets.newLinkedHashSet(); + if (jsonElement.getAsJsonObject().get("security_groups") != null) { + JsonArray x = jsonElement.getAsJsonObject().get("security_groups").getAsJsonArray(); + for(JsonElement y : x) { + names.add(y.getAsJsonObject().get("name").getAsString()); + } + result.securityGroupNames(names); + } + return result.build(); + } + } } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index f2956abe61..7544be56d4 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -75,6 +75,7 @@ public class NovaRestClientModule extends RestClientModule metadata = Maps.newHashMap(); + protected String uuid; + protected String tenantId; + protected String userId; + protected Date updated; + protected Date created; + protected String hostId; + protected String accessIPv4; + protected String accessIPv6; + protected Status status; + protected String configDrive; + protected Resource image; + protected Resource flavor; + protected Map metadata = Maps.newHashMap(); // TODO: get gson multimap ad - private Multimap addresses = LinkedHashMultimap.create(); - private String adminPass; - private String keyName; + protected Multimap addresses = LinkedHashMultimap.create(); + protected String adminPass; + protected String keyName; /** * @see Server#getUuid() @@ -375,8 +375,7 @@ public class Server extends Resource { } /** - * - * @return host identifier, or null if in {@link ServerState#BUILD} + * @return host identifier, or null if in {@link Status#BUILD} */ @Nullable public String getHostId() { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java new file mode 100644 index 0000000000..e718305913 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java @@ -0,0 +1,271 @@ +/** + * 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.v1_1.domain; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.openstack.domain.Link; +import org.jclouds.openstack.domain.Resource; + +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * Extended server returned by ServerWithSecurityGroupsClient + * + * @author Adam Lowe + * @see org.jclouds.openstack.nova.v1_1.extensions.ServerWithSecurityGroupsClient + * @see + */ +public class ServerWithSecurityGroups extends Server { + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder().fromServerWithSecurityGroups(this); + } + + public static class Builder extends Server.Builder { + private Set securityGroupNames = Sets.newLinkedHashSet(); + + /** + * @see ServerWithSecurityGroups#getSecurityGroupNames() + */ + public Builder securityGroupNames(Set securityGroupNames) { + this.securityGroupNames = securityGroupNames; + return this; + } + + public Builder fromServerWithSecurityGroups(ServerWithSecurityGroups in) { + return fromServer(in).securityGroupNames(in.getSecurityGroupNames()); + } + + @Override + public ServerWithSecurityGroups build() { + return new ServerWithSecurityGroups(id, name, links, uuid, tenantId, userId, updated, created, hostId, accessIPv4, accessIPv6, + status, configDrive, image, flavor, adminPass, keyName, addresses, metadata, securityGroupNames); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder fromResource(Resource in) { + return Builder.class.cast(super.fromResource(in)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder fromServer(Server in) { + return Builder.class.cast(super.fromServer(in)); + } + + + /** + * {@inheritDoc} + */ + @Override + public Builder id(String id) { + return Builder.class.cast(super.id(id)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder name(String name) { + return Builder.class.cast(super.name(name)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder links(Set links) { + return Builder.class.cast(super.links(links)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder links(Link... links) { + return Builder.class.cast(super.links(links)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder uuid(String uuid) { + return Builder.class.cast(super.uuid(uuid)); + } + /** + * {@inheritDoc} + */ + @Override + public Builder tenantId(String tenantId) { + return Builder.class.cast(super.tenantId(tenantId)); + } + /** + * {@inheritDoc} + */ + @Override + public Builder userId(String userId) { + return Builder.class.cast(super.userId(userId)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder updated(Date updated) { + return Builder.class.cast(super.updated(updated)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder created(Date created) { + return Builder.class.cast(super.created(created)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder hostId(String hostId) { + return Builder.class.cast(super.hostId(hostId)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder accessIPv4(String accessIPv4) { + return Builder.class.cast(super.accessIPv4(accessIPv4)); + } + /** + * {@inheritDoc} + */ + @Override + public Builder accessIPv6(String accessIPv6) { + return Builder.class.cast(super.accessIPv6(accessIPv6)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder status(Status status) { + return Builder.class.cast(super.status(status)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder configDrive(String configDrive) { + return Builder.class.cast(super.configDrive(configDrive)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder image(Resource image) { + return Builder.class.cast(super.image(image)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder flavor(Resource flavor) { + return Builder.class.cast(super.flavor(flavor)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder adminPass(String adminPass) { + return Builder.class.cast(super.adminPass(adminPass)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder keyName(String keyName) { + return Builder.class.cast(super.keyName(keyName)); + } + + /** + * {@inheritDoc} + */ + @Override + public Builder addresses(Multimap addresses) { + return Builder.class.cast(super.addresses(addresses)); + } + /** + * {@inheritDoc} + */ + @Override + public Builder metadata(Map metadata) { + return Builder.class.cast(super.metadata(metadata)); + } + } + + @SerializedName("security_groups") + private final Set securityGroupNames; + + public ServerWithSecurityGroups(String id, String name, Set links, @Nullable String uuid, String tenantId, + String userId, Date updated, Date created, @Nullable String hostId, + @Nullable String accessIPv4, @Nullable String accessIPv6, Status status, + @Nullable String configDrive, Resource image, Resource flavor, String adminPass, + @Nullable String keyName, Multimap addresses, + Map metadata, Set securityGroupNames) { + super(id, name, links, uuid, tenantId, userId, updated, created, hostId, accessIPv4, accessIPv6, status, configDrive, image, flavor, adminPass, keyName, addresses, metadata); + this.securityGroupNames = ImmutableSet.copyOf(securityGroupNames); + } + + public Set getSecurityGroupNames() { + return Collections.unmodifiableSet(securityGroupNames); + } + + protected ToStringHelper string() { + return super.string().add("securityGroupNames", securityGroupNames); + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java new file mode 100644 index 0000000000..3277b671da --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java @@ -0,0 +1,61 @@ +/** + * 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.v1_1.extensions; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; + +import org.jclouds.openstack.filters.AuthenticateRequest; +import org.jclouds.openstack.nova.v1_1.domain.ServerWithSecurityGroups; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.SelectJson; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides synchronous access to Servers with Security Groups. + * + * @see org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient + * @see ServerWithSecurityGroupsClient + * @author Adam Lowe + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.CREATESERVEREXT) +@SkipEncoding({'/', '='}) +@RequestFilters(AuthenticateRequest.class) +public interface ServerWithSecurityGroupsAsyncClient { + + /** + * @see ServerWithSecurityGroupsClient#getServer(String) + */ + @GET + @SelectJson("server") + @Consumes(MediaType.APPLICATION_JSON) + @Path("/os-create-server-ext/{id}") + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture getServer(@PathParam("id") String id); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java new file mode 100644 index 0000000000..84c8d87b4e --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java @@ -0,0 +1,49 @@ +/** + * 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.v1_1.extensions; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.concurrent.Timeout; +import org.jclouds.openstack.nova.v1_1.domain.ServerWithSecurityGroups; +import org.jclouds.openstack.services.Extension; +import org.jclouds.openstack.services.ServiceType; + +/** + * Provides synchronous access to Server details including security groups. + *

+ * NOTE: the equivalent to listServersInDetail() doesn't work, so not extending ServerClient at this time. + * + * @author Adam Lowe + * @see org.jclouds.openstack.nova.v1_1.features.ServerClient + * @see ServerWithSecurityGroupsAsyncClient + */ +@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.CREATESERVEREXT) +@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) +public interface ServerWithSecurityGroupsClient { + + /** + * Retrieve details of the specified server, including security groups + * + * @param id id of the server + * @return server or null if not found + */ + ServerWithSecurityGroups getServer(String id); + +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java index b55e0830f2..7975cfeda9 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/functions/PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.java @@ -69,7 +69,9 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio .put(URI.create(ExtensionNamespaces.VOLUMES), URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1")) .put(URI.create(ExtensionNamespaces.VIRTUAL_INTERFACES), - URI.create("http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1")) + URI.create("http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1")) + .put(URI.create(ExtensionNamespaces.CREATESERVEREXT), + URI.create("http://docs.openstack.org/compute/ext/createserverext/api/v1.1")) .build(); @Inject diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java new file mode 100644 index 0000000000..6ad9738cba --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java @@ -0,0 +1,64 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.openstack.nova.v1_1.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +import java.net.URI; + +import org.jclouds.openstack.nova.v1_1.domain.ServerWithSecurityGroups; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * Tests parsing and guice wiring of ServerExtraDataClient + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "ServerWithSecurityGroupsClientExpectTest") +public class ServerWithSecurityGroupsClientExpectTest extends BaseNovaClientExpectTest { + + public void testGetServerWithSecurityGroups() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-create-server-ext/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); + ServerWithSecurityGroupsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(200).payload(payloadFromResource("/server_with_security_groups.json")).build() + ).getServerExtraDataExtensionForZone("az-1.region-a.geo-1").get(); + + ServerWithSecurityGroups server = client.getServer("8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); + assertEquals(server.getId(), "8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); + assertEquals(server.getSecurityGroupNames(), ImmutableSet.of("default", "group1")); + } + + public void testGetServerWithSecurityGroupsFailNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/os-create-server-ext/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); + ServerWithSecurityGroupsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).build(), + standardResponseBuilder(404).build() + ).getServerExtraDataExtensionForZone("az-1.region-a.geo-1").get(); + assertNull(client.getServer("8d0a6ca5-8849-4b3d-b86e-f24c92490ebb")); + } +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java new file mode 100644 index 0000000000..351a08ee45 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java @@ -0,0 +1,82 @@ +/** + * 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.v1_1.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.ServerWithSecurityGroups; +import org.jclouds.openstack.nova.v1_1.features.ServerClient; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of ServerWithSecurityGroupsClient + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "ServerWithSecurityGroupsClientLiveTest", singleThreaded = true) +public class ServerWithSecurityGroupsClientLiveTest extends BaseNovaClientLiveTest { + private ServerClient serverClient; + private Optional clientOption; + private String zone; + + @BeforeGroups(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); + serverClient = novaContext.getApi().getServerClientForZone(zone); + clientOption = novaContext.getApi().getServerExtraDataExtensionForZone(zone); + } + + public void testGetServer() { + if (clientOption.isPresent()) { + + for (Resource server : serverClient.listServers()) { + ServerWithSecurityGroups serverWithGroups = clientOption.get().getServer(server.getId()); + assertEquals(serverWithGroups.getId(), server.getId()); + assertEquals(serverWithGroups.getName(), server.getName()); + assertNotNull(serverWithGroups.getSecurityGroupNames()); + } + + // Create a new server to verify the groups work as expected + Server testServer = null; + try { + testServer = createServerInZone(zone); + + ServerWithSecurityGroups results = clientOption.get().getServer(testServer.getId()); + assertEquals(results.getId(), testServer.getId()); + assertEquals(results.getSecurityGroupNames(), ImmutableSet.of("default")); + } finally { + if (testServer != null) { + serverClient.deleteServer(testServer.getId()); + } + } + } + } + +} \ No newline at end of file diff --git a/apis/openstack-nova/src/test/resources/server_with_security_groups.json b/apis/openstack-nova/src/test/resources/server_with_security_groups.json new file mode 100644 index 0000000000..16e03fe9e1 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/server_with_security_groups.json @@ -0,0 +1 @@ +{"server": {"status": "ACTIVE", "updated": "2012-05-04T12:15:01Z", "hostId": "02c7c81e36024d2bfdb473cb762900138bc07777922479d3d4f8f690", "user_id": "1e8a56719e0d4ab4b7edb85c77f7290f", "name": "test", "links": [{"href": "http://172.16.89.148:8774/v2/4287930c796741aa898425f40832cb3c/servers/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "rel": "self"}, {"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832cb3c/servers/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "rel": "bookmark"}], "created": "2012-05-04T12:14:57Z", "tenant_id": "4287930c796741aa898425f40832cb3c", "image": {"id": "ea17cc36-f7c9-40cd-b6bf-a952b74870f2", "links": [{"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832cb3c/images/ea17cc36-f7c9-40cd-b6bf-a952b74870f2", "rel": "bookmark"}]}, "addresses": {"private": [{"version": 4, "addr": "10.0.0.8"}]}, "accessIPv4": "", "accessIPv6": "", "key_name": "", "progress": 0, "flavor": {"id": "1", "links": [{"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832cb3c/flavors/1", "rel": "bookmark"}]}, "config_drive": "", "id": "8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "security_groups": [{"name": "default"},{"name": "group1"}], "metadata": {}}} \ No newline at end of file From e95e6df8054992eda5e9639dfe07e2ff0f6a0e75 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Fri, 4 May 2012 14:59:31 +0100 Subject: [PATCH 4/7] openstack-nova: Adjusting Resource and it's descendants to new builder pattern --- .../openstack/nova/v1_1/domain/Extension.java | 122 +++-- .../openstack/nova/v1_1/domain/Flavor.java | 102 ++-- .../openstack/nova/v1_1/domain/Image.java | 184 ++++---- .../openstack/nova/v1_1/domain/Server.java | 435 ++++++++++++------ .../v1_1/domain/ServerWithSecurityGroups.java | 262 ++--------- .../jclouds/openstack/domain/Resource.java | 111 +++-- .../keystone/v2_0/domain/ApiMetadata.java | 99 ++-- 7 files changed, 641 insertions(+), 674 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Extension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Extension.java index 17b756f491..6291c495c7 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Extension.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Extension.java @@ -18,15 +18,13 @@ */ package org.jclouds.openstack.nova.v1_1.domain; -import static com.google.common.base.Objects.toStringHelper; - import java.net.URI; import java.util.Date; -import java.util.Set; -import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; +import com.google.common.base.Objects; + /** * The OpenStack Compute API is extensible. Extensions serve two purposes: They * allow the introduction of new features in the API without requiring a version @@ -39,94 +37,86 @@ import org.jclouds.openstack.domain.Resource; * /> */ public class Extension extends Resource { - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return builder().fromExtension(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromExtension(this); } - public static class Builder extends Resource.Builder { - + public static abstract class Builder> extends Resource.Builder { private URI namespace; private String alias; private Date updated; private String description; - public Builder namespace(URI namespace) { + /** + * @see Extension#getNamespace() + */ + public T namespace(URI namespace) { this.namespace = namespace; - return this; + return self(); } - public Builder alias(String alias) { + /** + * @see Extension#getAlias() + */ + public T alias(String alias) { + id(alias); this.alias = alias; - return this; + return self(); } - public Builder updated(Date updated) { + /** + * @see Extension#getUpdated() + */ + public T updated(Date updated) { this.updated = updated; - return this; + return self(); } - public Builder description(String description) { + /** + * @see Extension#getDescription() + */ + public T description(String description) { this.description = description; - return this; + return self(); } public Extension build() { - return new Extension(name, links, namespace, alias, updated, description); + return new Extension(this); } - public Builder fromExtension(Extension in) { - return fromResource(in).namespace(in.getNamespace()).alias(in.getAlias()).updated(in.getUpdated()) - .description(in.getDescription()); + public T fromExtension(Extension in) { + return super.fromResource(in) + .namespace(in.getNamespace()) + .alias(in.getAlias()) + .updated(in.getUpdated()) + .description(in.getDescription()) + ; } - /** - * {@inheritDoc} - */ + } + + private static class ConcreteBuilder extends Builder { @Override - public Builder id(String id) { - return alias(id); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); + protected ConcreteBuilder self() { + return this; } } - private URI namespace; - private String alias; - private Date updated; - private String description; + private final URI namespace; + private final String alias; + private final Date updated; + private final String description; - protected Extension(String name, Set links, URI namespace, String alias, Date updated, String description) { - super(alias, name, links); - this.namespace = namespace; - this.alias = alias; - this.updated = updated; - this.description = description; + protected Extension(Builder builder) { + super(builder); + this.namespace = builder.namespace; + this.alias = builder.alias; + this.updated = builder.updated; + this.description = builder.description; } public URI getNamespace() { @@ -151,9 +141,11 @@ public class Extension extends Resource { } @Override - public String toString() { - return toStringHelper("").add("id", getId()).add("name", name).add("links", links).add("namespace", namespace) - .add("alias", alias).add("updated", updated).add("description", description).toString(); + public Objects.ToStringHelper string() { + return super.string() + .add("namespace", namespace) + .add("alias", alias) + .add("updated", updated) + .add("description", description); } - } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Flavor.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Flavor.java index 1c372ddeee..12d37cc2a3 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Flavor.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Flavor.java @@ -18,13 +18,10 @@ */ package org.jclouds.openstack.nova.v1_1.domain; -import static com.google.common.base.Objects.toStringHelper; - -import java.util.Set; - -import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; +import com.google.common.base.Objects; + /** * A flavor is an available hardware configuration for a server. Each flavor has * a unique combination of disk space and memory capacity. @@ -35,73 +32,61 @@ import org.jclouds.openstack.domain.Resource; * /> */ public class Flavor extends Resource { - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return builder().fromFlavor(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromFlavor(this); } - public static class Builder extends Resource.Builder { - + public static abstract class Builder> extends Resource.Builder { private int ram; private int disk; private int vcpus; - public Builder ram(int ram) { + /** + * @see Flavor#getRam() + */ + public T ram(int ram) { this.ram = ram; - return this; + return self(); } - public Builder disk(int disk) { + /** + * @see Flavor#getDisk() + */ + public T disk(int disk) { this.disk = disk; - return this; + return self(); } - public Builder vcpus(int vcpus) { + /** + * @see Flavor#getVcpus() + */ + public T vcpus(int vcpus) { this.vcpus = vcpus; - return this; + return self(); } public Flavor build() { - return new Flavor(id, name, links, ram, disk, vcpus); + return new Flavor(this); } - public Builder fromFlavor(Flavor in) { - return fromResource(in).ram(in.getRam()).disk(in.getDisk()).vcpus(in.getVcpus()); + public T fromFlavor(Flavor in) { + return super.fromResource(in) + .ram(in.getRam()) + .disk(in.getDisk()) + .vcpus(in.getVcpus()) + ; } - /** - * {@inheritDoc} - */ + } + + private static class ConcreteBuilder extends Builder { @Override - public Builder id(String id) { - return Builder.class.cast(super.id(id)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); + protected ConcreteBuilder self() { + return this; } } @@ -109,11 +94,11 @@ public class Flavor extends Resource { private int disk; private int vcpus; - protected Flavor(String id, String name, Set links, int ram, int disk, int vcpus) { - super(id, name, links); - this.ram = ram; - this.disk = disk; - this.vcpus = vcpus; + protected Flavor(Builder builder) { + super(builder); + this.ram = builder.ram; + this.disk = builder.disk; + this.vcpus = builder.vcpus; } public int getRam() { @@ -129,9 +114,10 @@ public class Flavor extends Resource { } @Override - public String toString() { - return toStringHelper("").add("id", id).add("name", name).add("links", links).add("ram", ram).add("disk", disk) - .add("vcpus", vcpus).toString(); + protected Objects.ToStringHelper string() { + return super.string() + .add("ram", ram) + .add("disk", disk) + .add("vcpus", vcpus); } - } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java index 132bd9ae53..9670ba096e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java @@ -18,15 +18,12 @@ */ package org.jclouds.openstack.nova.v1_1.domain; -import static com.google.common.base.Objects.toStringHelper; - import java.util.Date; import java.util.Map; -import java.util.Set; -import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; +import com.google.common.base.Objects; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; @@ -68,117 +65,130 @@ public class Image extends Resource { } - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return builder().fromImage(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromImage(this); } - public static class Builder extends Resource.Builder { - + public static abstract class Builder> extends Resource.Builder { private Date updated; private Date created; private String tenantId; private String userId; - private Status status; + private Image.Status status; private int progress; private int minDisk; private int minRam; private Resource server; - private Map metadata = Maps.newLinkedHashMap(); + private Map metadata = ImmutableMap.of(); - public Builder updated(Date updated) { + /** + * @see Image#getUpdated() + */ + public T updated(Date updated) { this.updated = updated; - return this; + return self(); } - public Builder created(Date created) { + /** + * @see Image#getCreated() + */ + public T created(Date created) { this.created = created; - return this; + return self(); } - public Builder tenantId(String tenantId) { + /** + * @see Image#getTenantId() + */ + public T tenantId(String tenantId) { this.tenantId = tenantId; - return this; + return self(); } - public Builder userId(String userId) { + /** + * @see Image#getUserId() + */ + public T userId(String userId) { this.userId = userId; - return this; + return self(); } - public Builder status(Status status) { + /** + * @see Image#getStatus() + */ + public T status(Image.Status status) { this.status = status; - return this; + return self(); } - public Builder progress(int progress) { + /** + * @see Image#getProgress() + */ + public T progress(int progress) { this.progress = progress; - return this; + return self(); } - public Builder minDisk(int minDisk) { + /** + * @see Image#getMinDisk() + */ + public T minDisk(int minDisk) { this.minDisk = minDisk; - return this; + return self(); } - public Builder minRam(int minRam) { + /** + * @see Image#getMinRam() + */ + public T minRam(int minRam) { this.minRam = minRam; - return this; + return self(); } - public Builder server(Resource server) { + /** + * @see Image#getServer() + */ + public T server(Resource server) { this.server = server; - return this; + return self(); } - public Builder metadata(Map metadata) { + /** + * @see Image#getMetadata() + */ + public T metadata(Map metadata) { this.metadata = metadata; - return this; + return self(); } public Image build() { - return new Image(id, name, links, updated, created, tenantId, userId, status, progress, minDisk, minRam, - server, metadata); + return new Image(this); } - public Builder fromImage(Image in) { - return fromResource(in).status(in.getStatus()).updated(in.getUpdated()).created(in.getCreated()).progress( - in.getProgress()).server(in.getServer()).metadata(in.getMetadata()); + public T fromImage(Image in) { + return super.fromResource(in) + .updated(in.getUpdated()) + .created(in.getCreated()) + .tenantId(in.getTenantId()) + .userId(in.getUserId()) + .status(in.getStatus()) + .progress(in.getProgress()) + .minDisk(in.getMinDisk()) + .minRam(in.getMinRam()) + .server(in.getServer()) + .metadata(in.getMetadata()); } - /** - * {@inheritDoc} - */ + } + + private static class ConcreteBuilder extends Builder { @Override - public Builder id(String id) { - return Builder.class.cast(super.id(id)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); + protected ConcreteBuilder self() { + return this; } } @@ -195,19 +205,18 @@ public class Image extends Resource { private final Resource server; private final Map metadata; - protected Image(String id, String name, Set links, Date updated, Date created, String tenantId, String userId, - Status status, int progress, int minDisk, int minRam, Resource server, Map metadata) { - super(id, name, links); - this.updated = updated; - this.created = created; - this.tenantId = tenantId; - this.userId = userId; - this.status = status; - this.progress = progress; - this.minDisk = minDisk; - this.minRam = minRam; - this.server = server; - this.metadata = ImmutableMap. copyOf(metadata); + protected Image(Builder builder) { + super(builder); + this.updated = builder.updated; + this.created = builder.created; + this.tenantId = builder.tenantId; + this.userId = builder.userId; + this.status = builder.status; + this.progress = builder.progress; + this.minDisk = builder.minDisk; + this.minRam = builder.minRam; + this.server = builder.server; + this.metadata = ImmutableMap.copyOf(builder.metadata); } public Date getUpdated() { @@ -252,11 +261,18 @@ public class Image extends Resource { } @Override - public String toString() { - return toStringHelper("").add("id", id).add("name", name).add("links", links).add("updated", updated).add( - "created", created).add("tenantId", tenantId).add("userId", userId).add("status", status).add( - "progress", progress).add("minDisk", minDisk).add("minRam", minRam).add("server", server).add( - "metadata", metadata).toString(); + protected Objects.ToStringHelper string() { + return super.string() + .add("updated", updated) + .add("created", created) + .add("tenantId", tenantId) + .add("userId", userId) + .add("status", status) + .add("progress", progress) + .add("minDisk", minDisk) + .add("minRam", minRam) + .add("server", server) + .add("metadata", metadata); } } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java index 6e2b128b27..e05c641a24 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java @@ -26,7 +26,6 @@ import java.util.Set; import org.jclouds.compute.domain.NodeState; import org.jclouds.javax.annotation.Nullable; -import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.util.Multimaps2; @@ -36,7 +35,6 @@ import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.gson.annotations.SerializedName; @@ -67,7 +65,7 @@ public class Server extends Resource { NodeState.PENDING), VERIFY_RESIZE(NodeState.PENDING), REVERT_RESIZE(NodeState.PENDING), PASSWORD( NodeState.PENDING), REBOOT(NodeState.PENDING), HARD_REBOOT(NodeState.PENDING), DELETED( NodeState.TERMINATED), UNKNOWN(NodeState.UNRECOGNIZED), ERROR(NodeState.ERROR), UNRECOGNIZED( - NodeState.UNRECOGNIZED); + NodeState.UNRECOGNIZED), PAUSED(NodeState.SUSPENDED); private final NodeState nodeState; @@ -92,216 +90,263 @@ public class Server extends Resource { } } - public static Builder builder() { - return new Builder(); + + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return builder().fromServer(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromServer(this); } - public static class Builder extends Resource.Builder { - protected String uuid; - protected String tenantId; - protected String userId; - protected Date updated; - protected Date created; - protected String hostId; - protected String accessIPv4; - protected String accessIPv6; - protected Status status; - protected String configDrive; - protected Resource image; - protected Resource flavor; - protected Map metadata = Maps.newHashMap(); - // TODO: get gson multimap ad - protected Multimap addresses = LinkedHashMultimap.create(); - protected String adminPass; - protected String keyName; + public static abstract class Builder> extends Resource.Builder { + private String uuid; + private String tenantId; + private String userId; + private Date updated; + private Date created; + private String hostId; + private String accessIPv4; + private String accessIPv6; + private Server.Status status; + private Resource image; + private Resource flavor; + private String adminPass; + private String keyName; + private String configDrive; + private Multimap addresses = ImmutableMultimap.of(); + private Map metadata = ImmutableMap.of(); + private String taskState; + private String vmState; + private String powerState; + private String instanceName; + private String hostName; + private String hypervisorName; + private String diskConfig; /** * @see Server#getUuid() */ - public Builder uuid(@Nullable String uuid) { + public T uuid(String uuid) { this.uuid = uuid; - return this; + return self(); } /** * @see Server#getTenantId() */ - public Builder tenantId(String tenantId) { + public T tenantId(String tenantId) { this.tenantId = tenantId; - return this; + return self(); } /** * @see Server#getUserId() */ - public Builder userId(String userId) { + public T userId(String userId) { this.userId = userId; - return this; + return self(); } /** * @see Server#getUpdated() */ - public Builder updated(Date updated) { + public T updated(Date updated) { this.updated = updated; - return this; + return self(); } /** * @see Server#getCreated() */ - public Builder created(Date created) { + public T created(Date created) { this.created = created; - return this; + return self(); } /** * @see Server#getHostId() */ - public Builder hostId(@Nullable String hostId) { + public T hostId(String hostId) { this.hostId = hostId; - return this; + return self(); } /** * @see Server#getAccessIPv4() */ - public Builder accessIPv4(@Nullable String accessIPv4) { + public T accessIPv4(String accessIPv4) { this.accessIPv4 = accessIPv4; - return this; + return self(); } /** * @see Server#getAccessIPv6() */ - public Builder accessIPv6(@Nullable String accessIPv6) { + public T accessIPv6(String accessIPv6) { this.accessIPv6 = accessIPv6; - return this; + return self(); } /** * @see Server#getStatus() */ - public Builder status(Status status) { + public T status(Server.Status status) { this.status = status; - return this; - } - - /** - * @see Server#getConfigDrive() - */ - public Builder configDrive(@Nullable String configDrive) { - this.configDrive = configDrive; - return this; + return self(); } /** * @see Server#getImage() */ - public Builder image(Resource image) { + public T image(Resource image) { this.image = image; - return this; + return self(); } /** - * @see Server#getImage() + * @see Server#getFlavor() */ - public Builder flavor(Resource flavor) { + public T flavor(Resource flavor) { this.flavor = flavor; - return this; + return self(); } - /** - * @see Server#getMetadata() - */ - public Builder metadata(Map metadata) { - this.metadata = ImmutableMap.copyOf(metadata); - return this; - } - - /** - * @see Server#getAddresses() - */ - public Builder addresses(Multimap addresses) { - this.addresses = ImmutableMultimap.copyOf(checkNotNull(addresses, "addresses")); - return this; - } - /** * @see Server#getAdminPass() */ - public Builder adminPass(String adminPass) { + public T adminPass(String adminPass) { this.adminPass = adminPass; - return this; + return self(); } /** * @see Server#getKeyName() */ - public Builder keyName(@Nullable String keyName) { + public T keyName(String keyName) { this.keyName = keyName; - return this; + return self(); + } + + /** + * @see Server#getConfigDrive() + */ + public T configDrive(String configDrive) { + this.configDrive = configDrive; + return self(); + } + + /** + * @see Server#getAddresses() + */ + public T addresses(Multimap addresses) { + this.addresses = addresses; + return self(); + } + + /** + * @see Server#getMetadata() + */ + public T metadata(Map metadata) { + this.metadata = metadata; + return self(); + } + + /** + * @see Server#getTaskState() + */ + public T taskState(String taskState) { + this.taskState = taskState; + return self(); + } + + /** + * @see Server#getVmState() + */ + public T vmState(String vmState) { + this.vmState = vmState; + return self(); + } + + /** + * @see Server#getPowerState() + */ + public T powerState(String powerState) { + this.powerState = powerState; + return self(); + } + + /** + * @see Server#getInstanceName() + */ + public T instanceName(String instanceName) { + this.instanceName = instanceName; + return self(); + } + + /** + * @see Server#getHostName() + */ + public T hostName(String hostName) { + this.hostName = hostName; + return self(); + } + + /** + * @see Server#getHypervisorName() + */ + public T hypervisorName(String hypervisorName) { + this.hypervisorName = hypervisorName; + return self(); + } + + /** + * @see Server#getDiskConfig() + */ + public T diskConfig(String diskConfig) { + this.diskConfig = diskConfig; + return self(); } - @Override public Server build() { - return new Server(id, name, links, uuid, tenantId, userId, updated, created, hostId, accessIPv4, accessIPv6, - status, configDrive, image, flavor, adminPass, keyName, addresses, metadata); + return new Server(this); } - public Builder fromServer(Server in) { - return fromResource(in).uuid(in.getUuid()).tenantId(in.getTenantId()).userId(in.getUserId()).updated( - in.getUpdated()).created(in.getCreated()).hostId(in.getHostId()).accessIPv4(in.getAccessIPv4()) - .accessIPv6(in.getAccessIPv6()).status(in.getStatus()).configDrive(in.getConfigDrive()).image( - in.getImage()).flavor(in.getFlavor()).adminPass(in.getAdminPass()).keyName(in.getKeyName()) - .addresses(in.getAddresses()).metadata(in.getMetadata()); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder id(String id) { - return Builder.class.cast(super.id(id)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Link... links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); + public T fromServer(Server in) { + return super.fromResource(in) + .uuid(in.getUuid()) + .tenantId(in.getTenantId()) + .userId(in.getUserId()) + .updated(in.getUpdated()) + .created(in.getCreated()) + .hostId(in.getHostId()) + .accessIPv4(in.getAccessIPv4()) + .accessIPv6(in.getAccessIPv6()) + .status(in.getStatus()) + .image(in.getImage()) + .flavor(in.getFlavor()) + .adminPass(in.getAdminPass()) + .keyName(in.getKeyName()) + .configDrive(in.getConfigDrive()) + .addresses(in.getAddresses()) + .metadata(in.getMetadata()) + .taskState(in.getTaskState()) + .vmState(in.getVmState()) + .powerState(in.getPowerState()) + .instanceName(in.getInstanceName()) + .hostName(in.getHostName()) + .hypervisorName(in.getHypervisorName()) + .diskConfig(in.getDiskConfig()); } } + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + protected final String uuid; @SerializedName("tenant_id") protected final String tenantId; @@ -324,28 +369,51 @@ public class Server extends Resource { protected final Map> addresses; protected final Map metadata; - protected Server(String id, String name, Set links, @Nullable String uuid, String tenantId, String userId, - Date updated, Date created, @Nullable String hostId, @Nullable String accessIPv4, - @Nullable String accessIPv6, Status status, @Nullable String configDrive, Resource image, Resource flavor, - String adminPass, @Nullable String keyName, Multimap addresses, - Map metadata) { - super(id, name, links); - this.uuid = uuid; // TODO: see what version this came up in - this.tenantId = checkNotNull(tenantId, "tenantId"); - this.userId = checkNotNull(userId, "userId"); - this.updated = checkNotNull(updated, "updated"); - this.created = checkNotNull(created, "created"); - this.hostId = hostId; - this.accessIPv4 = accessIPv4; - this.accessIPv6 = accessIPv6; - this.status = checkNotNull(status, "status"); - this.configDrive = configDrive; - this.image = checkNotNull(image, "image"); - this.flavor = checkNotNull(flavor, "flavor"); - this.metadata = Maps.newHashMap(metadata); - this.addresses = Multimaps2.toOldSchool(ImmutableMultimap.copyOf(checkNotNull(addresses, "addresses"))); - this.adminPass = adminPass; - this.keyName = keyName; + // Extended status extension + @SerializedName("OS-EXT-STS:task_state") + protected final String taskState; + @SerializedName("OS-EXT-STS:vm_state") + protected final String vmState; + @SerializedName("OS-EXT-STS:power_state") + protected final String powerState; + + // Extended server attributes extension + @SerializedName("OS-EXT-SRV-ATTR:instance_name") + protected final String instanceName; + @SerializedName("OS-EXT-SRV-ATTR:host") + protected final String hostName; + @SerializedName("OS-EXT-SRV-ATTR:hypervisor_hostname") + protected final String hypervisorName; + + // Disk Config extension + @SerializedName("OS-DCF:diskConfig") + protected final String diskConfig; + + protected Server(Builder builder) { + super(builder); + this.uuid = builder.uuid; // TODO: see what version this came up in + this.tenantId = checkNotNull(builder.tenantId, "tenantId"); + this.userId = checkNotNull(builder.userId, "userId"); + this.updated = checkNotNull(builder.updated, "updated"); + this.created = checkNotNull(builder.created, "created"); + this.hostId = builder.hostId; + this.accessIPv4 = builder.accessIPv4; + this.accessIPv6 = builder.accessIPv6; + this.status = checkNotNull(builder.status, "status"); + this.configDrive = builder.configDrive; + this.image = checkNotNull(builder.image, "image"); + this.flavor = checkNotNull(builder.flavor, "flavor"); + this.metadata = Maps.newHashMap(builder.metadata); + this.addresses = Multimaps2.toOldSchool(ImmutableMultimap.copyOf(checkNotNull(builder.addresses, "addresses"))); + this.adminPass = builder.adminPass; + this.keyName = builder.keyName; + this.taskState = builder.taskState; + this.vmState = builder.vmState; + this.powerState = builder.powerState; + this.instanceName = builder.instanceName; + this.hostName = builder.hostName; + this.hypervisorName = builder.hypervisorName; + this.diskConfig = builder.diskConfig; } /** @@ -438,6 +506,79 @@ public class Server extends Resource { return keyName; } + + /** + * State of task running against this instance (e.g. "suspending") + *

+ * NOTE: This field is only present if the Extended Status extension is installed. + */ + @Nullable + public String getTaskState() { + return this.taskState; + } + + /** + * State of task running against this instance (e.g. "suspending") + *

+ * NOTE: This field is only present if the Extended Status extension is installed. + */ + @Nullable + public String getVmState() { + return this.vmState; + } + + /** + * State of task running against this instance (e.g. "suspending") + *

+ * NOTE: This field is only present if the Extended Status extension is installed. + */ + @Nullable + public String getPowerState() { + return this.powerState; + } + + /** + * The name of the instance? + *

+ * NOTE: This field is only present if the The Extended Server Attributes API extension is installed. + */ + @Nullable + public String getInstanceName() { + return this.instanceName; + } + + /** + * The host name of the host this Server is running on + *

+ * NOTE: This field is only present if the The Extended Server Attributes API extension is installed. + * @see #getHostId() + */ + @Nullable + public String getHostName() { + return this.hostName; + } + + /** + * The name of the hypervisor this Server is running on + *

+ * NOTE: This field is only present if the The Extended Server Attributes API extension is installed. + */ + @Nullable + public String getHypervisorName() { + return this.hypervisorName; + } + + /** + * State of task running against this instance (e.g. "suspending") + *

+ * NOTE: This field is only present if the Disk Config extension is installed. + */ + @Nullable + public String getDiskConfig() { + return this.diskConfig; + } + + // hashCode/equals from super is ok @Override diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java index e718305913..bfe1d3ca83 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerWithSecurityGroups.java @@ -18,254 +18,92 @@ */ package org.jclouds.openstack.nova.v1_1.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Collections; -import java.util.Date; -import java.util.Map; import java.util.Set; -import org.jclouds.javax.annotation.Nullable; -import org.jclouds.openstack.domain.Link; -import org.jclouds.openstack.domain.Resource; - +import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; import com.google.gson.annotations.SerializedName; /** * Extended server returned by ServerWithSecurityGroupsClient * * @author Adam Lowe - * @see org.jclouds.openstack.nova.v1_1.extensions.ServerWithSecurityGroupsClient * @see +"http://docs.openstack.org/api/openstack-compute/1.1/content/Get_Server_Details-d1e2623.html" +/> */ public class ServerWithSecurityGroups extends Server { - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return new Builder().fromServerWithSecurityGroups(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromServerWithSecurityGroups(this); } - public static class Builder extends Server.Builder { - private Set securityGroupNames = Sets.newLinkedHashSet(); + public static abstract class Builder> extends Server.Builder { + private Set securityGroupNames = ImmutableSet.of(); /** * @see ServerWithSecurityGroups#getSecurityGroupNames() */ - public Builder securityGroupNames(Set securityGroupNames) { + public T securityGroupNames(Set securityGroupNames) { this.securityGroupNames = securityGroupNames; + return self(); + } + + public ServerWithSecurityGroups build() { + return new ServerWithSecurityGroups(this); + } + + public T fromServerWithSecurityGroups(ServerWithSecurityGroups in) { + return super.fromServer(in).securityGroupNames(in.getSecurityGroupNames()); + } + + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { return this; } - - public Builder fromServerWithSecurityGroups(ServerWithSecurityGroups in) { - return fromServer(in).securityGroupNames(in.getSecurityGroupNames()); - } - - @Override - public ServerWithSecurityGroups build() { - return new ServerWithSecurityGroups(id, name, links, uuid, tenantId, userId, updated, created, hostId, accessIPv4, accessIPv6, - status, configDrive, image, flavor, adminPass, keyName, addresses, metadata, securityGroupNames); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromServer(Server in) { - return Builder.class.cast(super.fromServer(in)); - } - - - /** - * {@inheritDoc} - */ - @Override - public Builder id(String id) { - return Builder.class.cast(super.id(id)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Link... links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder uuid(String uuid) { - return Builder.class.cast(super.uuid(uuid)); - } - /** - * {@inheritDoc} - */ - @Override - public Builder tenantId(String tenantId) { - return Builder.class.cast(super.tenantId(tenantId)); - } - /** - * {@inheritDoc} - */ - @Override - public Builder userId(String userId) { - return Builder.class.cast(super.userId(userId)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder updated(Date updated) { - return Builder.class.cast(super.updated(updated)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder created(Date created) { - return Builder.class.cast(super.created(created)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder hostId(String hostId) { - return Builder.class.cast(super.hostId(hostId)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder accessIPv4(String accessIPv4) { - return Builder.class.cast(super.accessIPv4(accessIPv4)); - } - /** - * {@inheritDoc} - */ - @Override - public Builder accessIPv6(String accessIPv6) { - return Builder.class.cast(super.accessIPv6(accessIPv6)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder status(Status status) { - return Builder.class.cast(super.status(status)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder configDrive(String configDrive) { - return Builder.class.cast(super.configDrive(configDrive)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder image(Resource image) { - return Builder.class.cast(super.image(image)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder flavor(Resource flavor) { - return Builder.class.cast(super.flavor(flavor)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder adminPass(String adminPass) { - return Builder.class.cast(super.adminPass(adminPass)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder keyName(String keyName) { - return Builder.class.cast(super.keyName(keyName)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder addresses(Multimap addresses) { - return Builder.class.cast(super.addresses(addresses)); - } - /** - * {@inheritDoc} - */ - @Override - public Builder metadata(Map metadata) { - return Builder.class.cast(super.metadata(metadata)); - } } - @SerializedName("security_groups") + @SerializedName(value="security_groups") private final Set securityGroupNames; - public ServerWithSecurityGroups(String id, String name, Set links, @Nullable String uuid, String tenantId, - String userId, Date updated, Date created, @Nullable String hostId, - @Nullable String accessIPv4, @Nullable String accessIPv6, Status status, - @Nullable String configDrive, Resource image, Resource flavor, String adminPass, - @Nullable String keyName, Multimap addresses, - Map metadata, Set securityGroupNames) { - super(id, name, links, uuid, tenantId, userId, updated, created, hostId, accessIPv4, accessIPv6, status, configDrive, image, flavor, adminPass, keyName, addresses, metadata); - this.securityGroupNames = ImmutableSet.copyOf(securityGroupNames); + protected ServerWithSecurityGroups(Builder builder) { + super(builder); + this.securityGroupNames = ImmutableSet.copyOf(checkNotNull(builder.securityGroupNames, "securityGroupNames")); } + /** + */ public Set getSecurityGroupNames() { - return Collections.unmodifiableSet(securityGroupNames); + return Collections.unmodifiableSet(this.securityGroupNames); + } + + @Override + public int hashCode() { + return Objects.hashCode(securityGroupNames); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + ServerWithSecurityGroups that = ServerWithSecurityGroups.class.cast(obj); + return super.equals(that) && Objects.equal(this.securityGroupNames, that.securityGroupNames); } protected ToStringHelper string() { - return super.string().add("securityGroupNames", securityGroupNames); + return super.string() + .add("securityGroupNames", securityGroupNames); } } \ No newline at end of file diff --git a/common/openstack/src/main/java/org/jclouds/openstack/domain/Resource.java b/common/openstack/src/main/java/org/jclouds/openstack/domain/Resource.java index dd72acd05f..1deb011da9 100644 --- a/common/openstack/src/main/java/org/jclouds/openstack/domain/Resource.java +++ b/common/openstack/src/main/java/org/jclouds/openstack/domain/Resource.java @@ -19,10 +19,9 @@ package org.jclouds.openstack.domain; -import static com.google.common.base.Objects.equal; -import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Collections; import java.util.Set; import org.jclouds.javax.annotation.Nullable; @@ -41,73 +40,86 @@ import com.google.common.collect.ImmutableSet; */ public class Resource implements Comparable { - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return builder().fromResource(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromResource(this); } - public static class Builder { - protected String id; - protected String name; - protected Set links = ImmutableSet.of(); + public static abstract class Builder> { + protected abstract T self(); + + private String id; + private String name; + private Set links = ImmutableSet.of(); /** * @see Resource#getId() */ - public Builder id(String id) { - this.id = checkNotNull(id, "id"); - return this; + public T id(String id) { + this.id = id; + return self(); } /** * @see Resource#getName() */ - public Builder name(@Nullable String name) { + public T name(String name) { this.name = name; - return this; + return self(); } /** * @see Resource#getLinks() */ - public Builder links(Link... links) { + public T links(Link... links) { return links(ImmutableSet.copyOf(checkNotNull(links, "links"))); } /** * @see Resource#getLinks() */ - public Builder links(Set links) { - this.links = ImmutableSet.copyOf(checkNotNull(links, "links")); - return this; + public T links(Set links) { + this.links = links; + return self(); } public Resource build() { - return new Resource(id, name, links); + return new Resource(this); } - public Builder fromResource(Resource from) { - return id(from.getId()).name(from.getName()).links(from.getLinks()); + public T fromResource(Resource in) { + return this + .id(in.getId()) + .name(in.getName()) + .links(in.getLinks()) + ; } } - protected final String id; - protected final String name; - protected final Set links; + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } - public Resource(String id,@Nullable String name, Set links) { - this.id = checkNotNull(id, "id"); - this.name = name; - this.links = ImmutableSet.copyOf(checkNotNull(links, "links")); + private final String id; + private final String name; + private final Set links; + + protected Resource(Builder builder) { + this.id = checkNotNull(builder.id, "id"); + this.name = builder.name; + this.links = ImmutableSet.copyOf(checkNotNull(builder.links, "links")); } /** * When providing an ID, it is assumed that the resource exists in the current OpenStack * deployment - * + * * @return the id of the resource in the current OpenStack deployment */ public String getId() { @@ -126,36 +138,35 @@ public class Resource implements Comparable { * @return the links of the id address allocated to the new server */ public Set getLinks() { - return links; - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object instanceof Resource) { - final Resource other = Resource.class.cast(object); - return equal(getId(), other.getId()) && equal(name, other.name) && equal(links, other.links); - } else { - return false; - } + return Collections.unmodifiableSet(this.links); } @Override public int hashCode() { - return Objects.hashCode(getId(), name, links); + return Objects.hashCode(id, name, links); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Resource that = Resource.class.cast(obj); + return Objects.equal(this.getId(), that.getId()) + && Objects.equal(this.name, that.name) + && Objects.equal(this.links, that.links); + } + + protected ToStringHelper string() { + return Objects.toStringHelper("") + .add("id", getId()) + .add("name", name) + .add("links", links); } @Override public String toString() { return string().toString(); } - - protected ToStringHelper string() { - return toStringHelper("").add("id", getId()).add("name", name).add("links", links); - } - @Override public int compareTo(Resource that) { if (that == null) diff --git a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/ApiMetadata.java b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/ApiMetadata.java index 61057d76da..ef4f0bfe48 100644 --- a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/ApiMetadata.java +++ b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/ApiMetadata.java @@ -25,7 +25,6 @@ import java.util.Date; import java.util.Set; import org.jclouds.javax.annotation.Nullable; -import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; import com.google.common.base.Objects; @@ -35,111 +34,96 @@ import com.google.common.collect.Sets; import com.google.gson.annotations.SerializedName; /** - * Class ApiMetadata + * @author Adam Lowe */ public class ApiMetadata extends Resource { - public static Builder builder() { - return new Builder(); + public static Builder builder() { + return new ConcreteBuilder(); } - public Builder toBuilder() { - return new Builder().fromApiMetadata(this); + public Builder toBuilder() { + return new ConcreteBuilder().fromApiMetadata(this); } - public static class Builder extends Resource.Builder { + public static abstract class Builder> extends Resource.Builder { private String status; private Date updated; private Set mediaTypes = Sets.newLinkedHashSet(); - public Builder status(String status) { + /** + * @see ApiMetadata#getStatus() + */ + public T status(String status) { this.status = status; - return this; + return self(); } - public Builder updated(Date updated) { + /** + * @see ApiMetadata#getUpdated() + */ + public T updated(Date updated) { this.updated = updated; - return this; + return self(); } - public Builder mediaTypes(Set mediaTypes) { + /** + * @see ApiMetadata#getMediaTypes() + */ + public T mediaTypes(Set mediaTypes) { this.mediaTypes = mediaTypes; - return this; + return self(); } public ApiMetadata build() { - return new ApiMetadata(id, name, links, updated, status, mediaTypes); + return new ApiMetadata(this); } - - public Builder fromApiMetadata(ApiMetadata in) { - return fromResource(in) + + public T fromApiMetadata(ApiMetadata in) { + return super.fromResource(in) .status(in.getStatus()) .updated(in.getUpdated()) .mediaTypes(in.getMediaTypes()); } - /** - * {@inheritDoc} - */ - @Override - public Builder id(String id) { - return Builder.class.cast(super.id(id)); - } + } - /** - * {@inheritDoc} - */ + private static class ConcreteBuilder extends Builder { @Override - public Builder name(String name) { - return Builder.class.cast(super.name(name)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder links(Set links) { - return Builder.class.cast(super.links(links)); - } - - /** - * {@inheritDoc} - */ - @Override - public Builder fromResource(Resource in) { - return Builder.class.cast(super.fromResource(in)); + protected ConcreteBuilder self() { + return this; } } - + + @Nullable private final String status; + @Nullable private final Date updated; - @SerializedName("media-types") + @SerializedName(value="media-types") + @Nullable private final Set mediaTypes; - protected ApiMetadata(String id, String name, Set links, Date updated, String status, Set mediaTypes) { - super(id, name, links); - this.status = status; - this.updated = updated; - this.mediaTypes = ImmutableSet.copyOf(checkNotNull(mediaTypes, "mediaTypes")); + protected ApiMetadata(Builder builder) { + super(builder); + this.status = checkNotNull(builder.status, "status"); + this.updated = checkNotNull(builder.updated, "updated"); + this.mediaTypes = ImmutableSet.copyOf(checkNotNull(builder.mediaTypes, "mediaTypes")); } /** */ - @Nullable public String getStatus() { return this.status; } /** */ - @Nullable public Date getUpdated() { return this.updated; } /** */ - @Nullable public Set getMediaTypes() { return Collections.unmodifiableSet(this.mediaTypes); } @@ -154,10 +138,9 @@ public class ApiMetadata extends Resource { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; ApiMetadata that = ApiMetadata.class.cast(obj); - return Objects.equal(this.status, that.status) + return super.equals(that) && Objects.equal(this.status, that.status) && Objects.equal(this.updated, that.updated) - && Objects.equal(this.mediaTypes, that.mediaTypes) - ; + && Objects.equal(this.mediaTypes, that.mediaTypes); } protected ToStringHelper string() { From 7678c6e776a2c898220dfa98d18d897fab50e805 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Fri, 4 May 2012 16:21:01 +0100 Subject: [PATCH 5/7] openstack-nova: Adding Admin Actions client to allow extra actions to be performed on servers --- .../openstack/nova/v1_1/NovaAsyncClient.java | 8 + .../openstack/nova/v1_1/NovaClient.java | 9 + .../v1_1/config/NovaRestClientModule.java | 1 + .../nova/v1_1/domain/BackupType.java | 43 ++ .../extensions/AdminActionsAsyncClient.java | 167 ++++++++ .../v1_1/extensions/AdminActionsClient.java | 123 ++++++ .../v1_1/extensions/ExtensionNamespaces.java | 5 +- ...paceEqualsAnyNamespaceInExtensionsSet.java | 2 + .../options/CreateBackupOfServerOptions.java | 107 +++++ .../AdminActionsClientExpectTest.java | 352 +++++++++++++++ .../AdminActionsClientLiveTest.java | 191 +++++++++ .../v1_1/internal/BaseNovaClientLiveTest.java | 12 +- .../test/resources/extension_list_full.json | 402 ++++++++++++------ 13 files changed, 1295 insertions(+), 127 deletions(-) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/BackupType.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsAsyncClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClient.java create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateBackupOfServerOptions.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientExpectTest.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientLiveTest.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index da1c3e16a9..78bec78997 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -23,6 +23,7 @@ import java.util.Set; import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.Zone; import org.jclouds.location.functions.ZoneToEndpoint; +import org.jclouds.openstack.nova.v1_1.extensions.AdminActionsAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; @@ -145,4 +146,11 @@ public interface NovaAsyncClient { Optional getServerExtraDataExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + /** + * Provides asynchronous access to Server Admin Actions features. + */ + @Delegate + Optional getAdminActionsExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index 3f1cbe269a..4bd53198f3 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -25,6 +25,7 @@ import org.jclouds.concurrent.Timeout; import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.Zone; import org.jclouds.location.functions.ZoneToEndpoint; +import org.jclouds.openstack.nova.v1_1.extensions.AdminActionsClient; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.extensions.HostAdministrationClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; @@ -148,4 +149,12 @@ public interface NovaClient { Optional getServerExtraDataExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + + /** + * Provides asynchronous access to Server Admin Actions features. + */ + @Delegate + Optional getAdminActionsExtensionForZone( + @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index 7544be56d4..83e727cec8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -76,6 +76,7 @@ public class NovaRestClientModule extends RestClientModule metadata = ImmutableMap.of(); + + @Override + public R bindToRequest(R request, Map postParams) { + Map data = Maps.newHashMap(); + data.putAll(postParams); + data.put("metadata", metadata); + return jsonBinder.bindToRequest(request, ImmutableMap.of("createBackup", data)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("createBackupOfServer is a POST operation"); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CreateBackupOfServerOptions)) return false; + final CreateBackupOfServerOptions other = CreateBackupOfServerOptions.class.cast(object); + return equal(metadata, other.metadata); + } + + @Override + public int hashCode() { + return Objects.hashCode(metadata); + } + + protected ToStringHelper string() { + return toStringHelper("").add("metadata", metadata); + } + + @Override + public String toString() { + return string().toString(); + } + + /** @see #getMetadata() */ + public CreateBackupOfServerOptions metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + /** + * Extra image properties to include + */ + public Map getMetadata() { + return metadata; + } + + public static class Builder { + /** + * @see CreateBackupOfServerOptions#getMetadata() + */ + public static CreateBackupOfServerOptions metadata(Map metadata) { + return new CreateBackupOfServerOptions().metadata(metadata); + } + } + +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientExpectTest.java new file mode 100644 index 0000000000..b7ef6aa329 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientExpectTest.java @@ -0,0 +1,352 @@ +/** + * 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.v1_1.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.net.URI; + +import javax.ws.rs.core.MediaType; + +import org.jclouds.http.HttpRequest; +import org.jclouds.openstack.nova.v1_1.domain.BackupType; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; +import org.jclouds.openstack.nova.v1_1.options.CreateBackupOfServerOptions; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; + +/** + * Tests parsing and guice wiring of AdminActionsClient + * + * @author Adam Lowe + */ +@Test(groups = "unit", testName = "AdminActionsClientExpectTest") +public class AdminActionsClientExpectTest extends BaseNovaClientExpectTest { + + public void testSuspend() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "suspend").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.suspendServer("1")); + } + + public void testSuspendFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "suspend").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.suspendServer("1")); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testSuspendFailsNotAuthorized() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "suspend").build(), + standardResponseBuilder(403).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + client.suspendServer("1"); + } + + public void testResume() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "resume").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.resumeServer("1")); + } + + public void testResumeFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "resume").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.resumeServer("1")); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testResumeFailsNotAuthorized() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "resume").build(), + standardResponseBuilder(403).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + client.resumeServer("1"); + } + + public void testLock() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "lock").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.lockServer("1")); + } + + public void testLockFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "lock").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.lockServer("1")); + } + + public void testUnlock() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "unlock").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.unlockServer("1")); + } + + public void testUnlockFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "unlock").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.unlockServer("1")); + } + + public void testPause() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "pause").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.pauseServer("1")); + } + + public void testPauseFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "pause").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.pauseServer("1")); + } + + public void testUnpause() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "unpause").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.unpauseServer("1")); + } + + public void testUnpauseFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "unpause").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.unpauseServer("1")); + } + + public void testMigrateServer() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "migrate").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.migrateServer("1")); + } + + + public void testMigrateServerFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "migrate").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.migrateServer("1")); + } + + public void testResetNetworkOfServer() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "resetNetwork").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.resetNetworkOfServer("1")); + } + + public void testResetNetworkOfServerFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "resetNetwork").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.resetNetworkOfServer("1")); + } + + public void testInjectNetworkInfoIntoServer() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "injectNetworkInfo").build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.injectNetworkInfoIntoServer("1")); + } + + public void testInjectNetworkInfoIntoServerFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "injectNetworkInfo").build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.injectNetworkInfoIntoServer("1")); + } + + public void testBackupServer() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"createBackup\":{\"backup_type\":\"weekly\",\"rotation\":\"3\",\"name\":\"mybackup\",\"metadata\":{\"some\":\"data or other\"}}}", MediaType.APPLICATION_JSON)).build(), + standardResponseBuilder(202).headers(ImmutableMultimap.of("Location", "http://172.16.89.149:8774/v2/images/1976b3b3-409a-468d-b16c-a9172c341b46")).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + String imageId = client.createBackupOfServer("1", "mybackup", BackupType.WEEKLY, 3, CreateBackupOfServerOptions.Builder.metadata(ImmutableMap.of("some", "data or other"))); + assertEquals(imageId, "1976b3b3-409a-468d-b16c-a9172c341b46"); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testBackupServerFailNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardRequestBuilder(endpoint).method("POST") + .payload(payloadFromStringWithContentType("{\"createBackup\":{\"backup_type\":\"weekly\",\"rotation\":\"3\",\"name\":\"mybackup\",\"metadata\":{\"some\":\"data or other\"}}}", MediaType.APPLICATION_JSON)).build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + client.createBackupOfServer("1", "mybackup", BackupType.WEEKLY, 3, CreateBackupOfServerOptions.Builder.metadata(ImmutableMap.of("some", "data or other"))); + } + + public void testLiveMigrateServer() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "GONNAOVERWRITE") + .payload(payloadFromStringWithContentType("{\"os-migrateLive\":{\"host\":\"bighost\",\"block_migration\":\"true\",\"disk_over_commit\":\"false\"}}", MediaType.APPLICATION_JSON)).build(), + standardResponseBuilder(202).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertTrue(client.liveMigrateServer("1", "bighost", true, false)); + } + + public void testLiveMigrateServerFailsNotFound() { + URI endpoint = URI.create("https://compute.north.host/v1.1/3456/servers/1/action"); + AdminActionsClient client = requestsSendResponses( + keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, + standardActionRequestBuilderVoidResponse(endpoint, "GONNAOVERWRITE") + .payload(payloadFromStringWithContentType("{\"os-migrateLive\":{\"host\":\"bighost\",\"block_migration\":\"true\",\"disk_over_commit\":\"false\"}}", MediaType.APPLICATION_JSON)).build(), + standardResponseBuilder(404).build() + ).getAdminActionsExtensionForZone("az-1.region-a.geo-1").get(); + + assertFalse(client.liveMigrateServer("1", "bighost", true, false)); + } + + protected HttpRequest.Builder standardActionRequestBuilderVoidResponse(URI endpoint, String actionName) { + return HttpRequest.builder().method("POST") + .headers(ImmutableMultimap.of("X-Auth-Token", authToken)) + .payload(payloadFromStringWithContentType("{\"" + actionName + "\":null}", MediaType.APPLICATION_JSON)) + .endpoint(endpoint); + } + +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientLiveTest.java new file mode 100644 index 0000000000..547675421e --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/AdminActionsClientLiveTest.java @@ -0,0 +1,191 @@ +/** + * 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.v1_1.extensions; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import org.jclouds.http.HttpResponseException; +import org.jclouds.openstack.nova.v1_1.domain.BackupType; +import org.jclouds.openstack.nova.v1_1.domain.Image; +import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.features.ExtensionClient; +import org.jclouds.openstack.nova.v1_1.features.ImageClient; +import org.jclouds.openstack.nova.v1_1.features.ServerClient; +import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest; +import org.jclouds.openstack.nova.v1_1.options.CreateBackupOfServerOptions; +import org.testng.annotations.AfterGroups; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +/** + * Tests behavior of HostAdministrationClient + * + * TODO test migration methods + * + * @author Adam Lowe + */ +@Test(groups = "live", testName = "AdminActionsClientLiveTest", singleThreaded = true) +public class AdminActionsClientLiveTest extends BaseNovaClientLiveTest { + private ImageClient imageClient; + private ServerClient serverClient; + private ExtensionClient extensionClient; + private Optional clientOption; + private String zone; + + private String testServerId; + private String backupImageId; + + @BeforeGroups(groups = {"integration", "live"}) + @Override + public void setupContext() { + super.setupContext(); + zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); + serverClient = novaContext.getApi().getServerClientForZone(zone); + extensionClient = novaContext.getApi().getExtensionClientForZone(zone); + imageClient = novaContext.getApi().getImageClientForZone(zone); + clientOption = novaContext.getApi().getAdminActionsExtensionForZone(zone); + if (clientOption.isPresent()) { + testServerId = createServerInZone(zone).getId(); + } + } + + @AfterGroups(groups = "live", alwaysRun = true) + @Override + protected void tearDown() { + if (clientOption.isPresent()) { + if (testServerId != null) { + assertTrue(novaContext.getApi().getServerClientForZone(zone).deleteServer(testServerId)); + } + if (backupImageId != null) { + imageClient.deleteImage(backupImageId); + } + } + super.tearDown(); + } + + @AfterMethod(alwaysRun = true) + public void ensureServerIsActiveAgain() { + blockUntilServerInState(testServerId, serverClient, Server.Status.ACTIVE); + } + + public void testSuspendAndResume() { + if (clientOption.isPresent()) { + AdminActionsClient client = clientOption.get(); + + // Suspend-resume + try { + client.resumeServer(testServerId); + fail("Resumed an active server!"); + } catch (HttpResponseException e) { + } + assertTrue(client.suspendServer(testServerId)); + blockUntilServerInState(testServerId, serverClient, Server.Status.SUSPENDED); + try { + client.suspendServer(testServerId); + fail("Suspended an already suspended server!"); + } catch (HttpResponseException e) { + } + assertTrue(client.resumeServer(testServerId)); + blockUntilServerInState(testServerId, serverClient, Server.Status.ACTIVE); + try { + client.resumeServer(testServerId); + fail("Resumed an already resumed server!"); + } catch (HttpResponseException e) { + } + } + } + + public void testLockAndUnlock() { + if (clientOption.isPresent()) { + AdminActionsClient client = clientOption.get(); + + // TODO should we be able to double-lock (as it were) + assertTrue(client.unlockServer(testServerId)); + assertTrue(client.unlockServer(testServerId)); + assertTrue(client.lockServer(testServerId)); + assertTrue(client.lockServer(testServerId)); + assertTrue(client.unlockServer(testServerId)); + assertTrue(client.unlockServer(testServerId)); + } + } + + public void testResetNetworkAndInjectNetworkInfo() { + if (clientOption.isPresent()) { + AdminActionsClient client = clientOption.get(); + assertTrue(client.resetNetworkOfServer(testServerId)); + assertTrue(client.injectNetworkInfoIntoServer(testServerId)); + } + } + + @Test + public void testPauseAndUnpause() { + if (clientOption.isPresent()) { + AdminActionsClient client = clientOption.get(); + + // Unlock and lock (double-checking error contitions too) + try { + client.unpauseServer(testServerId); + fail("Unpaused active server!"); + } catch (HttpResponseException e) { + } + assertTrue(client.pauseServer(testServerId)); + blockUntilServerInState(testServerId, serverClient, Server.Status.PAUSED); + try { + client.pauseServer(testServerId); + fail("paused a paused server!"); + } catch (HttpResponseException e) { + } + assertTrue(client.unpauseServer(testServerId)); + blockUntilServerInState(testServerId, serverClient, Server.Status.ACTIVE); + try { + client.unpauseServer(testServerId); + fail("Unpaused a server we just unpaused!"); + } catch (HttpResponseException e) { + } + } + } + + @Test + public void testCreateBackupOfServer() throws InterruptedException { + if (clientOption.isPresent()) { + backupImageId = clientOption.get().createBackupOfServer(testServerId, "jclouds-test-backup", BackupType.DAILY, 0, + CreateBackupOfServerOptions.Builder.metadata(ImmutableMap.of("test", "metadata"))); + + assertNotNull(backupImageId); + + // If we don't have extended task status, we'll have to wait here! + if (extensionClient.getExtensionByAlias("OS-EXT-STS") == null) { + Thread.sleep(30000); + } + + blockUntilServerInState(testServerId, serverClient, Server.Status.ACTIVE); + + Image backupImage = imageClient.getImage(backupImageId); + assertEquals(backupImage.getId(), backupImageId); + } + } +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java index 99151fefaa..f5b9b025a5 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/internal/BaseNovaClientLiveTest.java @@ -83,15 +83,19 @@ public class BaseNovaClientLiveTest extends BaseComputeServiceContextLiveTest { protected Server createServerInZone(String zoneId) { ServerClient serverClient = novaContext.getApi().getServerClientForZone(zoneId); Server server = serverClient.createServer("test", imageIdForZone(zoneId), flavorRefForZone(zoneId)); - blockUntilServerActive(server.getId(), serverClient); + blockUntilServerInState(server.getId(), serverClient, Status.ACTIVE); return server; } - private void blockUntilServerActive(String serverId, ServerClient client) { + /** + * Will block until the requested server is in the correct state, if Extended Server Status extension is loaded + * this will continue to block while any task is in progress. + */ + protected void blockUntilServerInState(String serverId, ServerClient client, Status status) { Server currentDetails = null; - for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != Status.ACTIVE; currentDetails = client + for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != status || currentDetails.getTaskState() != null; currentDetails = client .getServer(serverId)) { - System.out.printf("blocking on status active%n%s%n", currentDetails); + System.out.printf("blocking on status %s%n%s%n", status, currentDetails); try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { diff --git a/apis/openstack-nova/src/test/resources/extension_list_full.json b/apis/openstack-nova/src/test/resources/extension_list_full.json index 298a2f2f4a..490bebc0fe 100644 --- a/apis/openstack-nova/src/test/resources/extension_list_full.json +++ b/apis/openstack-nova/src/test/resources/extension_list_full.json @@ -1,123 +1,281 @@ +{"extensions": [ { - "extensions": [{ - "updated": "2011-06-09T00:00:00+00:00", - "name": "Multinic", - "links": [], - "namespace": "https://docs.openstack.org/ext/multinic/api/v1.1", - "alias": "NMN", - "description": "Multiple network support" - }, { - "updated": "2011-06-29T00:00:00+00:00", - "name": "Hosts", - "links": [], - "namespace": "https://docs.openstack.org/ext/hosts/api/v1.1", - "alias": "os-hosts", - "description": "Host administration" - }, { - "updated": "2011-03-25T00:00:00+00:00", - "name": "Volumes", - "links": [], - "namespace": "https://docs.openstack.org/ext/volumes/api/v1.1", - "alias": "os-volumes", - "description": "Volumes support" - }, { - "updated": "2011-05-25 16:12:21.656723", - "name": "Admin Controller", - "links": [], - "namespace": "https:TODO/", - "alias": "ADMIN", - "description": "The Admin API Extension" - }, { - "updated": "2011-08-08T00:00:00+00:00", - "name": "Quotas", - "links": [], - "namespace": "https://docs.openstack.org/ext/quotas-sets/api/v1.1", - "alias": "os-quota-sets", - "description": "Quotas management support" - }, { - "updated": "2011-08-24T00:00:00+00:00", - "name": "VolumeTypes", - "links": [], - "namespace": "https://docs.openstack.org/ext/volume_types/api/v1.1", - "alias": "os-volume-types", - "description": "Volume types support" - }, { - "updated": "2011-06-23T00:00:00+00:00", - "name": "FlavorExtraSpecs", - "links": [], - "namespace": "https://docs.openstack.org/ext/flavor_extra_specs/api/v1.1", - "alias": "os-flavor-extra-specs", - "description": "Instance type (flavor) extra specs" - }, { - "updated": "2011-09-14T00:00:00+00:00", - "name": "FlavorExtraData", - "links": [], - "namespace": "https://docs.openstack.org/ext/flavor_extra_data/api/v1.1", - "alias": "os-flavor-extra-data", - "description": "Provide additional data for flavors" - }, { - "updated": "2011-08-17T00:00:00+00:00", - "name": "VirtualInterfaces", - "links": [], - "namespace": "https://docs.openstack.org/ext/virtual_interfaces/api/v1.1", - "alias": "virtual_interfaces", - "description": "Virtual interface support" - }, { - "updated": "2011-07-19T00:00:00+00:00", - "name": "Createserverext", - "links": [], - "namespace": "https://docs.openstack.org/ext/createserverext/api/v1.1", - "alias": "os-create-server-ext", - "description": "Extended support to the Create Server v1.1 API" - }, { - "updated": "2011-08-08T00:00:00+00:00", - "name": "Keypairs", - "links": [], - "namespace": "https://docs.openstack.org/ext/keypairs/api/v1.1", - "alias": "os-keypairs", - "description": "Keypair Support" - }, { - "updated": "2011-08-25T00:00:00+00:00", - "name": "VSAs", - "links": [], - "namespace": "https://docs.openstack.org/ext/vsa/api/v1.1", - "alias": "zadr-vsa", - "description": "Virtual Storage Arrays support" - }, { - "updated": "2011-08-19T00:00:00+00:00", - "name": "SimpleTenantUsage", - "links": [], - "namespace": "https://docs.openstack.org/ext/os-simple-tenant-usage/api/v1.1", - "alias": "os-simple-tenant-usage", - "description": "Simple tenant usage extension" - }, { - "updated": "2011-08-18T00:00:00+00:00", - "name": "Rescue", - "links": [], - "namespace": "https://docs.openstack.org/ext/rescue/api/v1.1", - "alias": "os-rescue", - "description": "Instance rescue mode" - }, { - "updated": "2011-07-21T00:00:00+00:00", - "name": "SecurityGroups", - "links": [], - "namespace": "https://docs.openstack.org/ext/securitygroups/api/v1.1", - "alias": "security_groups", - "description": "Security group support" - }, { - "updated": "2011-06-16T00:00:00+00:00", - "name": "Floating_ips", - "links": [], - "namespace": "https://docs.openstack.org/ext/floating_ips/api/v1.1", - "alias": "os-floating-ips", - "description": "Floating IPs support" - }, { - "updated": "2011-06-16T00:00:00+00:00", - "name": "Users", - "links": [], - "namespace": "http://docs.openstack.org/compute/ext/users/api/v1.1", - "alias": "os-users", - "description": "Users support" - } - ] -} \ No newline at end of file + "updated": "2011-09-27T00:00:00+00:00", + "name": "DiskConfig", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/disk_config/api/v1.1", + "alias": "OS-DCF", + "description": "Disk Management Extension" +}, +{ + "updated": "2011-06-29T00:00:00+00:00", + "name": "Hosts", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/hosts/api/v1.1", + "alias": "os-hosts", + "description": "Admin-only host administration" +}, +{ + "updated": "2011-07-19T00:00:00+00:00", + "name": "SchedulerHints", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/scheduler-hints/api/v2", + "alias": "os-scheduler-hints", + "description": "Pass arbitrary key/value pairs to the scheduler" +}, +{ + "updated": "2011-08-08T00:00:00+00:00", + "name": "Quotas", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/quotas-sets/api/v1.1", + "alias": "os-quota-sets", + "description": "Quotas management support" +}, +{ + "updated": "2011-12-23T00:00:00+00:00", + "name": "Floating_ip_dns", + "links": [], + "namespace": "http://docs.openstack.org/ext/floating_ip_dns/api/v1.1", + "alias": "os-floating-ip-dns", + "description": "Floating IP DNS support" +}, +{ + "updated": "2011-09-14T00:00:00+00:00", + "name": "FlavorExtraData", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1", + "alias": "OS-FLV-EXT-DATA", + "description": "Provide additional data for flavors" +}, +{ + "updated": "2011-06-23T00:00:00+00:00", + "name": "FlavorExtraSpecs", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/flavor_extra_specs/api/v1.1", + "alias": "os-flavor-extra-specs", + "description": "Instance type (flavor) extra specs" +}, +{ + "updated": "2011-08-17T00:00:00+00:00", + "name": "VirtualInterfaces", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1", + "alias": "virtual_interfaces", + "description": "Virtual interface support" +}, +{ + "updated": "2011-12-23T00:00:00+00:00", + "name": "Accounts", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/accounts/api/v1.1", + "alias": "os-accounts", + "description": "Admin-only access to accounts" +}, +{ + "updated": "2011-03-25T00:00:00+00:00", + "name": "Volumes", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/volumes/api/v1.1", + "alias": "os-volumes", + "description": "Volumes support" +}, +{ + "updated": "2011-11-03T00:00:00+00:00", + "name": "ExtendedStatus", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/extended_status/api/v1.1", + "alias": "OS-EXT-STS", + "description": "Extended Status support" +}, +{ + "updated": "2011-12-23T00:00:00+00:00", + "name": "Consoles", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/os-consoles/api/v2", + "alias": "os-consoles", + "description": "Interactive Console support." +}, +{ + "updated": "2011-07-21T00:00:00+00:00", + "name": "SecurityGroups", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/securitygroups/api/v1.1", + "alias": "security_groups", + "description": "Security group support" +}, +{ + "updated": "2012-01-12T00:00:00+00:00", + "name": "Aggregates", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/aggregates/api/v1.1", + "alias": "os-aggregates", + "description": "Admin-only aggregate administration" +}, +{ + "updated": "2011-07-19T00:00:00+00:00", + "name": "Createserverext", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/createserverext/api/v1.1", + "alias": "os-create-server-ext", + "description": "Extended support to the Create Server v1.1 API" +}, +{ + "updated": "2011-09-01T00:00:00+00:00", + "name": "DeferredDelete", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1", + "alias": "os-deferred-delete", + "description": "Instance deferred delete" +}, +{ + "updated": "2011-12-21T00:00:00+00:00", + "name": "ServerDiagnostics", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/server-diagnostics/api/v1.1", + "alias": "os-server-diagnostics", + "description": "Allow Admins to view server diagnostics through server action" +}, +{ + "updated": "2011-12-23T00:00:00+00:00", + "name": "Networks", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/networks/api/v1.1", + "alias": "os-networks", + "description": "Admin-only Network Management Extension" +}, +{ + "updated": "2011-11-03T00:00:00+00:00", + "name": "ExtendedServerAttributes", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/extended_status/api/v1.1", + "alias": "OS-EXT-SRV-ATTR", + "description": "Extended Server Attributes support." +}, +{ + "updated": "2011-08-08T00:00:00+00:00", + "name": "Keypairs", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/keypairs/api/v1.1", + "alias": "os-keypairs", + "description": "Keypair Support" +}, +{ + "updated": "2011-08-24T00:00:00+00:00", + "name": "VolumeTypes", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/volume_types/api/v1.1", + "alias": "os-volume-types", + "description": "Volume types support" +}, +{ + "updated": "2011-08-19T00:00:00+00:00", + "name": "SimpleTenantUsage", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1", + "alias": "os-simple-tenant-usage", + "description": "Simple tenant usage extension" +}, +{ + "updated": "2012-01-04T00:00:00+00:00", + "name": "Floating_ip_pools", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/floating_ip_pools/api/v1.1", + "alias": "os-floating-ip-pools", + "description": "Floating IPs support" +}, +{ + "updated": "2012-01-23T00:00:00+00:00", + "name": "ServerStartStop", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/servers/api/v1.1", + "alias": "os-server-start-stop", + "description": "Start/Stop instance compute API support" +}, +{ + "updated": "2012-03-12T00:00:00+00:00", + "name": "QuotaClasses", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1", + "alias": "os-quota-class-sets", + "description": "Quota classes management support" +}, +{ + "updated": "2012-01-19T00:00:00+00:00", + "name": "Certificates", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/certificates/api/v1.1", + "alias": "os-certificates", + "description": "Certificates support" +}, +{ + "updated": "2011-08-18T00:00:00+00:00", + "name": "Rescue", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/rescue/api/v1.1", + "alias": "os-rescue", + "description": "Instance rescue mode" +}, +{ + "updated": "2012-01-19T00:00:00+00:00", + "name": "FlavorManage", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/flavor_manage/api/v1.1", + "alias": "os-flavor-manage", + "description": "\n Flavor create/delete API support\n " +}, +{ + "updated": "2011-12-16T00:00:00+00:00", + "name": "Cloudpipe", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/cloudpipe/api/v1.1", + "alias": "os-cloudpipe", + "description": "Adds actions to create cloudpipe instances.\n\n When running with the Vlan network mode, you need a mechanism to route\n from the public Internet to your vlans. This mechanism is known as a\n cloudpipe.\n\n At the time of creating this class, only OpenVPN is supported. Support for\n a SSH Bastion host is forthcoming.\n " +}, +{ + "updated": "2011-06-09T00:00:00+00:00", + "name": "Multinic", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/multinic/api/v1.1", + "alias": "NMN", + "description": "Multiple network support" +}, +{ + "updated": "2011-08-08T00:00:00+00:00", + "name": "Users", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/users/api/v1.1", + "alias": "os-users", + "description": "Allow admins to acces user information" +}, +{ + "updated": "2011-09-20T00:00:00+00:00", + "name": "AdminActions", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/admin-actions/api/v1.1", + "alias": "os-admin-actions", + "description": "Enable admin-only server actions\n\n Actions include: pause,unpause, suspend, resume, migrate,\n resetNetwork, injectNetworkInfo, lock, unlock, createBackup\n " +}, +{ + "updated": "2011-12-21T00:00:00+00:00", + "name": "ServerActionList", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/server-actions-list/api/v1.1", + "alias": "os-server-action-list", + "description": "Allow Admins to view pending server actions" +}, +{ + "updated": "2011-12-08T00:00:00+00:00", + "name": "Console_output", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/os-console-output/api/v2", + "alias": "os-console-output", + "description": "Console log output support, with tailing ability." +}, +{ + "updated": "2011-06-16T00:00:00+00:00", + "name": "Floating_ips", + "links": [], + "namespace": "http://docs.openstack.org/compute/ext/floating_ips/api/v1.1", + "alias": "os-floating-ips", + "description": "Floating IPs support"} +]} \ No newline at end of file From 33b4a2d25321c13476f32dd5a65b516b12e98c12 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Fri, 4 May 2012 16:44:32 +0100 Subject: [PATCH 6/7] openstack-nova: Wiring AdminActions extension into compute service to implement suspend and resume --- .../v1_1/compute/NovaComputeServiceAdapter.java | 12 ++++++++++-- .../v1_1/compute/NovaComputeServiceLiveTest.java | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java index 5dc476a9d0..1bc2e8a52e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java @@ -219,12 +219,20 @@ public class NovaComputeServiceAdapter implements @Override public void resumeNode(String id) { - throw new UnsupportedOperationException("suspend not supported"); + ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(id); + if (novaClient.getAdminActionsExtensionForZone(zoneAndId.getZone()).isPresent()) { + novaClient.getAdminActionsExtensionForZone(zoneAndId.getZone()).get().resumeServer(zoneAndId.getId()); + } + throw new UnsupportedOperationException("resume requires installation of the Admin Actions extension"); } @Override public void suspendNode(String id) { - throw new UnsupportedOperationException("suspend not supported"); + ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(id); + if (novaClient.getAdminActionsExtensionForZone(zoneAndId.getZone()).isPresent()) { + novaClient.getAdminActionsExtensionForZone(zoneAndId.getZone()).get().suspendServer(zoneAndId.getId()); + } + throw new UnsupportedOperationException("suspend requires installation of the Admin Actions extension"); } } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceLiveTest.java index df697bc88a..8dca31a1c3 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceLiveTest.java @@ -1,10 +1,13 @@ package org.jclouds.openstack.nova.v1_1.compute; +import static java.util.logging.Logger.getAnonymousLogger; + import java.util.Properties; import org.jclouds.compute.internal.BaseComputeServiceLiveTest; import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; import org.jclouds.openstack.nova.v1_1.config.NovaProperties; +import org.jclouds.rest.AuthorizationException; import org.jclouds.sshj.config.SshjSshClientModule; import org.testng.annotations.Test; @@ -31,9 +34,16 @@ public class NovaComputeServiceLiveTest extends BaseComputeServiceLiveTest { // start call is blocking anyway. } - @Test(enabled = true, dependsOnMethods = "testReboot", expectedExceptions = UnsupportedOperationException.class) + @Test(enabled = true, dependsOnMethods = "testReboot") public void testSuspendResume() throws Exception { - super.testSuspendResume(); + 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") From 653d5ccc4be4208ca662681edfaea528f52b75f2 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Fri, 4 May 2012 17:07:14 +0100 Subject: [PATCH 7/7] openstack-nova: Adjusting names of ServerWithSecurityGroups related calls --- .../org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java | 2 +- .../java/org/jclouds/openstack/nova/v1_1/NovaClient.java | 7 ++----- .../java/org/jclouds/openstack/nova/v1_1/domain/Image.java | 2 +- .../extensions/ServerWithSecurityGroupsAsyncClient.java | 5 +++-- .../v1_1/extensions/ServerWithSecurityGroupsClient.java | 7 +++++-- .../ServerWithSecurityGroupsClientExpectTest.java | 6 +++--- .../extensions/ServerWithSecurityGroupsClientLiveTest.java | 2 +- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index 78bec78997..7a16d7c194 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -143,7 +143,7 @@ public interface NovaAsyncClient { * Provides asynchronous access to Server Extra Data features. */ @Delegate - Optional getServerExtraDataExtensionForZone( + Optional getServerWithSecurityGroupsExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); /** diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index 4bd53198f3..75d3173bbd 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -126,7 +126,6 @@ public interface NovaClient { Optional getSimpleTenantUsageExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); - /** * Provides synchronous access to Volume features. */ @@ -141,17 +140,15 @@ public interface NovaClient { Optional getVirtualInterfaceExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); - /** * Provides synchronous access to Server Extra Data features. */ @Delegate - Optional getServerExtraDataExtensionForZone( + Optional getServerWithSecurityGroupsExtensionForZone( @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); - /** - * Provides asynchronous access to Server Admin Actions features. + * Provides synchronous access to Server Admin Actions features. */ @Delegate Optional getAdminActionsExtensionForZone( diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java index 9670ba096e..28196bf454 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java @@ -160,7 +160,7 @@ public class Image extends Resource { /** * @see Image#getMetadata() */ - public T metadata(Map metadata) { + public T metadata(Map metadata) { this.metadata = metadata; return self(); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java index 3277b671da..423d7ec97c 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsAsyncClient.java @@ -38,10 +38,11 @@ import com.google.common.util.concurrent.ListenableFuture; /** * Provides synchronous access to Servers with Security Groups. - * + * + * @author Adam Lowe * @see org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient * @see ServerWithSecurityGroupsClient - * @author Adam Lowe + * @see */ @Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.CREATESERVEREXT) @SkipEncoding({'/', '='}) diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java index 84c8d87b4e..3fab8ec83b 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClient.java @@ -26,13 +26,16 @@ import org.jclouds.openstack.services.Extension; import org.jclouds.openstack.services.ServiceType; /** - * Provides synchronous access to Server details including security groups. + * Provides synchronous access to Server details including security group, referred to as the CREATESERVEREXT extension + * in the nova documentation *

- * NOTE: the equivalent to listServersInDetail() doesn't work, so not extending ServerClient at this time. + * NOTE: the equivalent to listServersInDetail() isn't available at the other end, so not extending ServerClient at this + * time. * * @author Adam Lowe * @see org.jclouds.openstack.nova.v1_1.features.ServerClient * @see ServerWithSecurityGroupsAsyncClient + * @see */ @Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.CREATESERVEREXT) @Timeout(duration = 180, timeUnit = TimeUnit.SECONDS) diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java index 6ad9738cba..237526a83a 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientExpectTest.java @@ -30,7 +30,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableSet; /** - * Tests parsing and guice wiring of ServerExtraDataClient + * Tests parsing and guice wiring of ServerWithSecurityGroupsClient * * @author Adam Lowe */ @@ -44,7 +44,7 @@ public class ServerWithSecurityGroupsClientExpectTest extends BaseNovaClientExpe responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, standardRequestBuilder(endpoint).build(), standardResponseBuilder(200).payload(payloadFromResource("/server_with_security_groups.json")).build() - ).getServerExtraDataExtensionForZone("az-1.region-a.geo-1").get(); + ).getServerWithSecurityGroupsExtensionForZone("az-1.region-a.geo-1").get(); ServerWithSecurityGroups server = client.getServer("8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); assertEquals(server.getId(), "8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"); @@ -58,7 +58,7 @@ public class ServerWithSecurityGroupsClientExpectTest extends BaseNovaClientExpe responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse, standardRequestBuilder(endpoint).build(), standardResponseBuilder(404).build() - ).getServerExtraDataExtensionForZone("az-1.region-a.geo-1").get(); + ).getServerWithSecurityGroupsExtensionForZone("az-1.region-a.geo-1").get(); assertNull(client.getServer("8d0a6ca5-8849-4b3d-b86e-f24c92490ebb")); } } \ No newline at end of file diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java index 351a08ee45..75a4227fdc 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/ServerWithSecurityGroupsClientLiveTest.java @@ -50,7 +50,7 @@ public class ServerWithSecurityGroupsClientLiveTest extends BaseNovaClientLiveTe super.setupContext(); zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova"); serverClient = novaContext.getApi().getServerClientForZone(zone); - clientOption = novaContext.getApi().getServerExtraDataExtensionForZone(zone); + clientOption = novaContext.getApi().getServerWithSecurityGroupsExtensionForZone(zone); } public void testGetServer() {