ServerPredicates to make waiting easier.

This commit is contained in:
Everett Toews 2014-04-25 17:01:58 -05:00
parent 852f0fb342
commit f1a0370bd0
6 changed files with 498 additions and 28 deletions

View File

@ -101,6 +101,18 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
<exclusions>
<!-- provided by the jclouds-bouncycastle driver -->
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View File

@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.predicates;
import com.google.common.base.Predicate;
import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
import org.jclouds.openstack.nova.v2_0.features.ServerApi;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
import static org.jclouds.util.Predicates2.retry;
/**
* This class tests to see if a Server or ServerCreated has reached a desired status. This class is most useful when
* paired with a RetryablePredicate as in the code below. Together these classes can be used to block execution until
* the Server or ServerCreated has reached that desired status. This is useful when your Server needs to be 100% ready
* before you can continue with execution.
* <p/>
* For example, you can use the factory methods like so.
* <p/>
* <pre>
* {@code
* ServerCreated serverCreated = serverApi.create("my-server", image.getId(), flavor.getId());
*
* if (!ServerPredicates.awaitActive(serverApi).apply(serverCreated.getId())) {
* throw new TimeoutException("Timeout on server: " + serverCreated);
* }
* </pre>
*
* <pre>
* {@code
* if (!ServerPredicates.awaitStatus(serverApi, ACTIVE, 300, 2).apply(server.getId())) {
* throw new TimeoutException("Timeout on server: " + serverCreated);
* }
* </pre>
*/
public class ServerPredicates {
private static final int TEN_MINUTES = 600;
private static final int FIVE_SECONDS = 5;
/**
* Waits until a Server is ACTIVE.
*
* @param serverApi The ServerApi in the zone where your Server resides.
* @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
*/
public static Predicate<String> awaitActive(ServerApi serverApi) {
return awaitStatus(serverApi, ACTIVE, TEN_MINUTES, FIVE_SECONDS);
}
/**
* Waits until a Server is SHUTOFF.
*
* @param serverApi The ServerApi in the zone where your Server resides.
* @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
*/
public static Predicate<String> awaitShutoff(ServerApi serverApi) {
return awaitStatus(serverApi, SHUTOFF, TEN_MINUTES, FIVE_SECONDS);
}
/**
* Waits until a Server reaches Status.
*
* @param serverApi The ServerApi in the zone where your Server resides.
* @return Predicate that will check the status every periodInSec seconds for a maximum of maxWaitInSec minutes.
*/
public static Predicate<String> awaitStatus(
ServerApi serverApi, Status status, long maxWaitInSec, long periodInSec) {
ServerStatusPredicate statusPredicate = new ServerStatusPredicate(serverApi, status);
return retry(statusPredicate, maxWaitInSec, periodInSec, periodInSec, SECONDS);
}
public static class ServerStatusPredicate implements Predicate<String> {
private final ServerApi serverApi;
private final Status status;
public ServerStatusPredicate(ServerApi serverApi, Status status) {
this.serverApi = checkNotNull(serverApi, "serverApi must be defined");
this.status = checkNotNull(status, "status must be defined");
}
/**
* @return boolean Return true when the Server reaches the Status, false otherwise
* @throws IllegalStateException if the Server associated with serverId does not exist
*/
@Override
public boolean apply(String serverId) {
checkNotNull(serverId, "server must be defined");
Server server = serverApi.get(serverId);
if (server == null) {
throw new IllegalStateException(String.format("Server %s not found.", serverId));
}
return status.equals(server.getStatus());
}
}
}

View File

@ -16,11 +16,14 @@
*/ */
package org.jclouds.openstack.nova.v2_0.features; package org.jclouds.openstack.nova.v2_0.features;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitActive;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertTrue;
import org.jclouds.http.HttpResponseException;
import org.jclouds.openstack.nova.v2_0.domain.Network; import org.jclouds.openstack.nova.v2_0.domain.Network;
import org.jclouds.openstack.nova.v2_0.domain.Server; import org.jclouds.openstack.nova.v2_0.domain.Server;
import org.jclouds.openstack.nova.v2_0.domain.ServerCreated; import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
@ -84,9 +87,9 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
for (String zoneId : zones) { for (String zoneId : zones) {
ServerApi serverApi = api.getServerApiForZone(zoneId); ServerApi serverApi = api.getServerApiForZone(zoneId);
try { try {
serverId = createServer(zoneId, "nova", Server.Status.ACTIVE).getId(); serverId = createServer(zoneId, "nova").getId();
Server server = serverApi.get(serverId); Server server = serverApi.get(serverId);
assertEquals(server.getStatus(), Server.Status.ACTIVE); assertEquals(server.getStatus(), ACTIVE);
} finally { } finally {
if (serverId!=null) { if (serverId!=null) {
serverApi.delete(serverId); serverApi.delete(serverId);
@ -114,9 +117,10 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
ServerCreated server = serverApi.create(hostName, imageIdForZone(zoneId), "1", options); ServerCreated server = serverApi.create(hostName, imageIdForZone(zoneId), "1", options);
serverId = server.getId(); serverId = server.getId();
blockUntilServerInState(server.getId(), serverApi, Server.Status.ACTIVE); awaitActive(serverApi).apply(server.getId());
Server serverCheck = serverApi.get(serverId); Server serverCheck = serverApi.get(serverId);
assertEquals(serverCheck.getStatus(), Server.Status.ACTIVE); assertEquals(serverCheck.getStatus(), ACTIVE);
} finally { } finally {
if (serverId != null) { if (serverId != null) {
serverApi.delete(serverId); serverApi.delete(serverId);
@ -131,9 +135,12 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
for (String zoneId : zones) { for (String zoneId : zones) {
ServerApi serverApi = api.getServerApiForZone(zoneId); ServerApi serverApi = api.getServerApiForZone(zoneId);
try { try {
serverId = createServer(zoneId, "err", Server.Status.ERROR).getId(); serverId = createServer(zoneId, "err").getId();
Server server = serverApi.get(serverId); } catch (HttpResponseException e) {
assertEquals(server.getStatus(), Server.Status.ERROR); // Here is an implementation detail difference between OpenStack and some providers.
// Some providers accept a bad availability zone and create the server in the zoneId.
// Vanilla OpenStack will error out with a 400 Bad Request
assertEquals(e.getResponse().getStatusCode(), 400);
} finally { } finally {
if (serverId!=null) { if (serverId!=null) {
serverApi.delete(serverId); serverApi.delete(serverId);
@ -150,11 +157,11 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
for (String zoneId : zones) { for (String zoneId : zones) {
ServerApi serverApi = api.getServerApiForZone(zoneId); ServerApi serverApi = api.getServerApiForZone(zoneId);
try { try {
serverId = createServer(zoneId, Server.Status.ACTIVE).getId(); serverId = createServer(zoneId, null).getId();
Server server = serverApi.get(serverId); Server server = serverApi.get(serverId);
assertEquals(server.getStatus(), Server.Status.ACTIVE); assertEquals(server.getStatus(), ACTIVE);
RebuildServerOptions options = new RebuildServerOptions(). RebuildServerOptions options = new RebuildServerOptions().
withImage(server.getImage().getId()). withImage(server.getImage().getId()).
@ -179,22 +186,18 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
} }
} }
private Server createServer(String regionId, Server.Status serverStatus) { private Server createServer(String zoneId, String availabilityZoneId) {
ServerApi serverApi = api.getServerApiForZone(regionId); ServerApi serverApi = api.getServerApiForZone(zoneId);
CreateServerOptions options = new CreateServerOptions(); CreateServerOptions options = new CreateServerOptions();
ServerCreated server = serverApi.create(hostName, imageIdForZone(regionId), flavorRefForZone(regionId), options); if (availabilityZoneId != null) {
options = options.availabilityZone(availabilityZoneId);
blockUntilServerInState(server.getId(), serverApi, serverStatus);
return serverApi.get(server.getId());
} }
private Server createServer(String regionId, String availabilityZoneId, Server.Status serverStatus) { ServerCreated server = serverApi.create(hostName, imageIdForZone(zoneId), flavorRefForZone(zoneId), options);
ServerApi serverApi = api.getServerApiForZone(regionId);
CreateServerOptions options = new CreateServerOptions(); awaitActive(serverApi).apply(server.getId());
options = options.availabilityZone(availabilityZoneId);
ServerCreated server = serverApi.create(hostName, imageIdForZone(regionId), flavorRefForZone(regionId), options);
blockUntilServerInState(server.getId(), serverApi, serverStatus);
return serverApi.get(server.getId()); return serverApi.get(server.getId());
} }

View File

@ -19,6 +19,7 @@ package org.jclouds.openstack.nova.v2_0.internal;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import com.google.common.collect.*;
import org.jclouds.apis.BaseApiLiveTest; import org.jclouds.apis.BaseApiLiveTest;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties; import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.NovaApi;
@ -35,9 +36,6 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Throwables; 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 NovaApi} * Tests behavior of {@code NovaApi}
@ -58,7 +56,15 @@ public class BaseNovaApiLiveTest extends BaseApiLiveTest<NovaApi> {
@Override @Override
public void setup() { public void setup() {
super.setup(); super.setup();
String testZone = System.getProperty("test." + provider + ".zone");
if (testZone != null) {
zones = ImmutableSet.of(testZone);
} else {
zones = api.getConfiguredZones(); zones = api.getConfiguredZones();
}
for (String zone : zones) { for (String zone : zones) {
ServerApi serverApi = api.getServerApiForZone(zone); ServerApi serverApi = api.getServerApiForZone(zone);
for (Resource server : serverApi.list().concat()) { for (Resource server : serverApi.list().concat()) {
@ -103,7 +109,9 @@ public class BaseNovaApiLiveTest extends BaseApiLiveTest<NovaApi> {
protected String imageIdForZone(String zoneId) { protected String imageIdForZone(String zoneId) {
ImageApi imageApi = api.getImageApiForZone(zoneId); ImageApi imageApi = api.getImageApiForZone(zoneId);
return Iterables.getLast(imageApi.list().concat()).getId();
// Get the first image from the list as it tends to be "lighter" and faster to start
return Iterables.get(imageApi.list().concat(), 0).getId();
} }
protected String flavorRefForZone(String zoneId) { protected String flavorRefForZone(String zoneId) {

View File

@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.predicates;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.features.ServerApi;
import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
import org.testng.annotations.Test;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.*;
import static org.testng.Assert.*;
@Test(groups = "unit", testName = "ServerPredicatesMockTest")
public class ServerPredicatesMockTest extends BaseOpenStackMockTest<NovaApi> {
public void testAwaitActive() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
String serverDetailsActive = stringFromResource("/server_details.json").replace("BUILD(scheduling)", ACTIVE.value());
server.enqueue(new MockResponse().setBody(serverDetailsActive));
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
boolean result = awaitActive(serverApi).apply("52415800-8b69-11e0-9b19-734f000004d2");
assertTrue(result);
assertEquals(server.getRequestCount(), 5);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
public void testAwaitShutoff() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
String serverDetailsShutoff = stringFromResource("/server_details.json").replace("BUILD(scheduling)", SHUTOFF.value());
server.enqueue(new MockResponse().setBody(serverDetailsShutoff));
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
boolean result = awaitShutoff(serverApi).apply("52415800-8b69-11e0-9b19-734f000004d2");
assertTrue(result);
assertEquals(server.getRequestCount(), 7);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
public void testAwaitTimeout() throws Exception {
MockWebServer server = mockOpenStackServer();
server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
for (int i=0; i < 20; i++) {
server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
}
try {
NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
boolean result = awaitStatus(serverApi, ACTIVE, 3, 1).apply("52415800-8b69-11e0-9b19-734f000004d2");
assertFalse(result);
assertAuthentication(server);
} finally {
server.shutdown();
}
}
}

View File

@ -0,0 +1,228 @@
{
"access": {
"metadata": {
"roles": [
"9fe2ff9ee4384b1894a90878d3e92bab",
"b926cb0f4e2642678735f86c2b06205e",
"33484487e73d4da0918a19b9c7e1f8ae",
"f2e54c2105fb49e29479af047115cebc"
],
"is_admin": 0
},
"user": {
"name":"joe",
"roles": [
{
"name":"_member_"
},
{
"name":"anotherrole"
},
{
"name":"heat_stack_owner"
},
{
"name":"Member"
}
],
"id":"8fbf8e68d36e4ac7bcf912a26213bd49",
"roles_links": [],
"username":"joe"
},
"serviceCatalog": [
{
"name":"nova",
"type":"compute",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
"id":"2122bcaa704343c19ad2578410d4961d",
"internalURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL/v2/da0d12be20394afb851716e10a49e4a7"
}
]
},
{
"name":"neutron",
"type":"network",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/",
"id":"65a4d3f13cfb49a6a57a04e205cc2158",
"internalURL":"URL/",
"region":"RegionOne",
"adminURL":"URL/"
}
]
},
{
"name":"cinderv2",
"type":"volumev2",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
"id":"31fe4d92eac44044b05be21c6f44cebc",
"internalURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL/v2/da0d12be20394afb851716e10a49e4a7"
}
]
},
{
"name":"trove",
"type":"database",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7",
"id":"06b7a7dbd25c4a01819c879700a9712a",
"internalURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7"
}
]
},
{
"name":"s3",
"type":"s3",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL",
"id":"93b0b67091324e8ba01b62ee0584994c",
"internalURL":"URL",
"region":"RegionOne",
"adminURL":"URL"
}
]
},
{
"name":"glance",
"type":"image",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL",
"id":"a542e91bcfa046bfa1bf2397356d1414",
"internalURL":"URL",
"region":"RegionOne",
"adminURL":"URL"
}
]
},
{
"name":"novav3",
"type":"computev3",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v3",
"id":"9c3e8abb576d483db93bcef70c67bc1d",
"internalURL":"URL/v3",
"region":"RegionOne",
"adminURL":"URL/v3"
}
]
},
{
"name":"heat",
"type":"cloudformation",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v1",
"id":"6f4ca5ca9698425b85c300b3fc176c39",
"internalURL":"URL/v1",
"region":"RegionOne",
"adminURL":"URL/v1"
}
]
},
{
"name":"cinder",
"type":"volume",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
"id":"037039c676694a35aa28d34fce09e51d",
"internalURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL/v1/da0d12be20394afb851716e10a49e4a7"
}
]
},
{
"name":"ec2",
"type":"ec2",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/services/Cloud",
"id":"1d242631bccb4ff4ba7a395dbcb51648",
"internalURL":"URL/services/Cloud",
"region":"RegionOne",
"adminURL":"URL/services/Admin"
}
]
},
{
"name":"heat",
"type":"orchestration",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
"id":"199d00075e4a40308a6ad2aa8980d0cd",
"internalURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL/v1/da0d12be20394afb851716e10a49e4a7"
}
]
},
{
"name":"swift",
"type":"object-store",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v1/AUTH_da0d12be20394afb851716e10a49e4a7",
"id":"26b2cb1efb044193b847fc3f2fb12e82",
"internalURL":"URL/v1/AUTH_da0d12be20394afb851716e10a49e4a7",
"region":"RegionOne",
"adminURL":"URL"
}
]
},
{
"name":"keystone",
"type":"identity",
"endpoints_links": [],
"endpoints": [
{
"publicURL":"URL/v2.0",
"id":"1bbfe80b50df4c4a84040aa782e42140",
"internalURL":"URL/v2.0",
"region":"RegionOne",
"adminURL":"URL/v2.0"
}
]
}
],
"token": {
"tenant": {
"name":"jclouds",
"id":"da0d12be20394afb851716e10a49e4a7",
"enabled": true,
"description": null
},
"id":"TOKEN",
"expires":"2014-04-28T22:48:24Z",
"issued_at":"2014-04-28T21:48:24.972896"
}
}
}