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