package io.airlift.airship.coordinator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.airlift.airship.shared.AgentStatus;
import io.airlift.airship.shared.Assignment;
import io.airlift.airship.shared.AssignmentRepresentation;
import io.airlift.airship.shared.MockUriInfo;
import io.airlift.airship.shared.SlotStatus;
import io.airlift.airship.shared.SlotStatusRepresentation;
import io.airlift.http.server.HttpServerConfig;
import io.airlift.http.server.HttpServerInfo;
import io.airlift.node.NodeInfo;
import io.airlift.units.Duration;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static io.airlift.airship.coordinator.CoordinatorSlotResource.MIN_PREFIX_SIZE;
import static io.airlift.airship.shared.AgentLifecycleState.ONLINE;
import static io.airlift.airship.shared.AssignmentHelper.APPLE_ASSIGNMENT;
import static io.airlift.airship.shared.AssignmentHelper.BANANA_ASSIGNMENT;
import static io.airlift.airship.shared.ExtraAssertions.assertEqualsNoOrder;
import static io.airlift.airship.shared.SlotLifecycleState.STOPPED;
import static io.airlift.airship.shared.SlotStatus.createSlotStatus;
import static io.airlift.airship.shared.Strings.shortestUniquePrefix;
import static java.lang.Math.min;
import static java.util.Arrays.asList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
public class TestCoordinatorSlotResource
{
private CoordinatorSlotResource resource;
private Coordinator coordinator;
private TestingMavenRepository repository;
private MockProvisioner provisioner;
@BeforeMethod
public void setUp()
throws Exception
{
NodeInfo nodeInfo = new NodeInfo("testing");
repository = new TestingMavenRepository();
provisioner = new MockProvisioner();
coordinator = new Coordinator(nodeInfo,
new HttpServerInfo(new HttpServerConfig(), nodeInfo),
new CoordinatorConfig().setStatusExpiration(new Duration(1, TimeUnit.DAYS)),
provisioner.getCoordinatorFactory(),
provisioner.getAgentFactory(),
repository,
provisioner,
new InMemoryStateManager(),
new MockServiceInventory());
resource = new CoordinatorSlotResource(coordinator, repository);
}
@AfterMethod
public void tearDown()
throws Exception
{
repository.destroy();
}
@Test
public void testGetAllSlots()
{
SlotStatus slot1 = createSlotStatus(UUID.randomUUID(),
URI.create("fake://localhost/v1/agent/slot/slot1"),
URI.create("fake://localhost/v1/agent/slot/slot1"),
"instance-id",
"/location",
STOPPED,
APPLE_ASSIGNMENT,
"/slot1",
ImmutableMap.<String, Integer>of());
SlotStatus slot2 = createSlotStatus(UUID.randomUUID(),
URI.create("fake://localhost/v1/agent/slot/slot2"),
URI.create("fake://localhost/v1/agent/slot/slot2"),
"instance-id",
"/location",
STOPPED,
APPLE_ASSIGNMENT,
"/slot2",
ImmutableMap.<String, Integer>of());
AgentStatus agentStatus = new AgentStatus(UUID.randomUUID().toString(),
ONLINE,
"instance-id",
URI.create("fake://foo/"),
URI.create("fake://foo/"),
"/unknown/location",
"instance.type",
ImmutableList.of(slot1, slot2),
ImmutableMap.<String, Integer>of());
provisioner.addAgents(agentStatus);
coordinator.updateAllAgentsAndWait();
int prefixSize = shortestUniquePrefix(asList(slot1.getId().toString(), slot2.getId().toString()), MIN_PREFIX_SIZE);
URI requestUri = URI.create("http://localhost/v1/slot");
Response response = resource.getAllSlots(MockUriInfo.from(requestUri));
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
assertEqualsNoOrder((Iterable<?>) response.getEntity(),
ImmutableList.of(SlotStatusRepresentation.from(slot1, prefixSize, repository), SlotStatusRepresentation.from(slot2, prefixSize, repository)));
assertNull(response.getMetadata().get("Content-Type")); // content type is set by jersey based on @Produces
}
@Test
public void testGetAllSlotsWithFilter()
{
SlotStatus slot1 = createSlotStatus(UUID.randomUUID(),
URI.create("fake://foo/v1/agent/slot/slot1"),
URI.create("fake://foo/v1/agent/slot/slot1"),
"instance-id",
"/location",
STOPPED,
APPLE_ASSIGNMENT,
"/slot1",
ImmutableMap.<String, Integer>of());
SlotStatus slot2 = createSlotStatus(UUID.randomUUID(),
URI.create("fake://bar/v1/agent/slot/slot2"),
URI.create("fake://bar/v1/agent/slot/slot2"),
"instance-id",
"/location",
STOPPED,
APPLE_ASSIGNMENT,
"/slot2",
ImmutableMap.<String, Integer>of());
AgentStatus agentStatus = new AgentStatus(UUID.randomUUID().toString(),
ONLINE,
"instance-id",
URI.create("fake://foo/"),
URI.create("fake://foo/"),
"/unknown/location",
"instance.type",
ImmutableList.of(slot1, slot2),
ImmutableMap.<String, Integer>of());
provisioner.addAgents(agentStatus);
coordinator.updateAllAgentsAndWait();
int prefixSize = shortestUniquePrefix(asList(slot1.getId().toString(), slot2.getId().toString()), MIN_PREFIX_SIZE);
URI requestUri = URI.create("http://localhost/v1/slot?host=foo");
Response response = resource.getAllSlots(MockUriInfo.from(requestUri));
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
assertEqualsNoOrder((Iterable<?>) response.getEntity(), ImmutableList.of(SlotStatusRepresentation.from(slot1, prefixSize, repository)));
assertNull(response.getMetadata().get("Content-Type")); // content type is set by jersey based on @Produces
}
@Test
public void testGetAllSlotEmpty()
{
URI requestUri = URI.create("http://localhost/v1/slot?state=unknown");
Response response = resource.getAllSlots(MockUriInfo.from(requestUri));
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
assertEqualsNoOrder((Iterable<?>) response.getEntity(), ImmutableList.of());
assertNull(response.getMetadata().get("Content-Type")); // content type is set by jersey based on @Produces
}
@Test
public void testInstallOne()
{
testInstall(1, 1, APPLE_ASSIGNMENT);
}
@Test
public void testInstallLimit()
{
testInstall(10, 3, APPLE_ASSIGNMENT);
}
@Test
public void testInstallNotEnoughAgents()
{
testInstall(3, 10, APPLE_ASSIGNMENT);
}
public void testInstall(int numberOfAgents, int limit, Assignment assignment)
{
for (int i = 0; i < numberOfAgents; i++) {
provisioner.addAgent(UUID.randomUUID().toString(), URI.create("fake://appleServer1/"), ImmutableMap.of("cpu", 8, "memory", 1024));
}
coordinator.updateAllAgentsAndWait();
UriInfo uriInfo = MockUriInfo.from("http://localhost/v1/slot/assignment?host=apple*");
Response response = resource.install(AssignmentRepresentation.from(assignment), limit, uriInfo, null);
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
Collection<SlotStatusRepresentation> slots = (Collection<SlotStatusRepresentation>) response.getEntity();
assertEquals(slots.size(), min(numberOfAgents, limit));
for (SlotStatusRepresentation slotRepresentation : slots) {
SlotStatus slot = slotRepresentation.toSlotStatus("instance");
assertEquals(slot.getAssignment(), assignment);
assertEquals(slot.getState(), STOPPED);
}
assertNull(response.getMetadata().get("Content-Type")); // content type is set by jersey based on @Produces
}
@Test
public void testInstallWithinResourceLimit()
{
provisioner.addAgent(UUID.randomUUID().toString(), URI.create("fake://appleServer1/"), ImmutableMap.of("cpu", 1, "memory", 512));
coordinator.updateAllAgentsAndWait();
UriInfo uriInfo = MockUriInfo.from("http://localhost/v1/slot/assignment");
Response response = resource.install(AssignmentRepresentation.from(APPLE_ASSIGNMENT), 1, uriInfo, null);
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
Collection<SlotStatusRepresentation> slots = (Collection<SlotStatusRepresentation>) response.getEntity();
assertEquals(slots.size(), 1);
for (SlotStatusRepresentation slotRepresentation : slots) {
assertAppleSlot(slotRepresentation);
}
assertNull(response.getMetadata().get("Content-Type")); // content type is set by jersey based on @Produces
}
@Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "No agents have the available resources to run the specified binary and configuration.")
public void testInstallNotEnoughResources()
{
provisioner.addAgent(UUID.randomUUID().toString(), URI.create("fake://appleServer1/"), ImmutableMap.of("cpu", 0, "memory", 0));
coordinator.updateAllAgentsAndWait();
UriInfo uriInfo = MockUriInfo.from("http://localhost/v1/slot/assignment");
Response response = resource.install(AssignmentRepresentation.from(APPLE_ASSIGNMENT), 1, uriInfo, null);
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
response.getEntity();
}
@Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "No agents have the available resources to run the specified binary and configuration.")
public void testInstallResourcesConsumed()
{
provisioner.addAgent(UUID.randomUUID().toString(), URI.create("fake://appleServer1/"), ImmutableMap.of("cpu", 1, "memory", 512));
coordinator.updateAllAgentsAndWait();
UriInfo uriInfo = MockUriInfo.from("http://localhost/v1/slot/assignment");
// install an apple server
Response response = resource.install(AssignmentRepresentation.from(APPLE_ASSIGNMENT), 1, uriInfo, null);
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
Collection<SlotStatusRepresentation> slots = (Collection<SlotStatusRepresentation>) response.getEntity();
assertEquals(slots.size(), 1);
assertAppleSlot(Iterables.get(slots, 0));
// try to install a banana server which will fail
response = resource.install(AssignmentRepresentation.from(BANANA_ASSIGNMENT), 1, uriInfo, null);
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
response.getEntity();
}
private void assertAppleSlot(SlotStatusRepresentation slotRepresentation)
{
SlotStatus slot = slotRepresentation.toSlotStatus("instance");
assertEquals(slot.getAssignment(), APPLE_ASSIGNMENT);
assertEquals(slot.getState(), STOPPED);
assertEquals(slot.getResources(), ImmutableMap.of("cpu", 1, "memory", 512));
}
}