package com.hubspot.singularity.mesos; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.apache.mesos.Protos; import org.apache.mesos.Protos.ContainerInfo.DockerInfo.PortMapping; import org.apache.mesos.Protos.ContainerInfo.Type; import org.apache.mesos.Protos.Environment.Variable; import org.apache.mesos.Protos.FrameworkID; import org.apache.mesos.Protos.Offer; import org.apache.mesos.Protos.OfferID; import org.apache.mesos.Protos.Parameter; import org.apache.mesos.Protos.SlaveID; import org.apache.mesos.Protos.Volume.Mode; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.hubspot.mesos.MesosUtils; import com.hubspot.mesos.Resources; import com.hubspot.mesos.SingularityContainerInfo; import com.hubspot.mesos.SingularityContainerType; import com.hubspot.mesos.SingularityDockerInfo; import com.hubspot.mesos.SingularityDockerNetworkType; import com.hubspot.mesos.SingularityDockerPortMapping; import com.hubspot.mesos.SingularityDockerVolumeMode; import com.hubspot.mesos.SingularityPortMappingType; import com.hubspot.mesos.SingularityVolume; import com.hubspot.singularity.RequestType; import com.hubspot.singularity.SingularityDeploy; import com.hubspot.singularity.SingularityDeployBuilder; import com.hubspot.singularity.SingularityPendingRequest.PendingType; import com.hubspot.singularity.SingularityPendingTask; import com.hubspot.singularity.SingularityPendingTaskId; import com.hubspot.singularity.SingularityRequest; import com.hubspot.singularity.SingularityRequestBuilder; import com.hubspot.singularity.SingularityTask; import com.hubspot.singularity.SingularityTaskRequest; import com.hubspot.singularity.config.NetworkConfiguration; import com.hubspot.singularity.config.SingularityConfiguration; import com.hubspot.singularity.data.ExecutorIdGenerator; public class SingularityMesosTaskBuilderTest { private final SingularityConfiguration configuration = new SingularityConfiguration(); private SingularityMesosTaskBuilder builder; private Resources taskResources; private Resources executorResources; private Offer offer; private SingularityPendingTask pendingTask; private final String user = "testUser"; @Before public void createMocks() { pendingTask = new SingularityPendingTask(new SingularityPendingTaskId("test", "1", 0, 1, PendingType.IMMEDIATE, 0), Optional.<List<String>> absent(), Optional.of(user), Optional.<String> absent(), Optional.<Boolean> absent(), Optional.<String> absent(), Optional.<Resources>absent(), Optional.<String>absent()); final SingularitySlaveAndRackHelper slaveAndRackHelper = mock(SingularitySlaveAndRackHelper.class); final ExecutorIdGenerator idGenerator = mock(ExecutorIdGenerator.class); when(idGenerator.getNextExecutorId()).then(new CreateFakeId()); builder = new SingularityMesosTaskBuilder(new ObjectMapper(), slaveAndRackHelper, idGenerator, configuration); taskResources = new Resources(1, 1, 0, 0); executorResources = new Resources(0.1, 1, 0, 0); offer = Offer.newBuilder() .setSlaveId(SlaveID.newBuilder().setValue("1")) .setId(OfferID.newBuilder().setValue("1")) .setFrameworkId(FrameworkID.newBuilder().setValue("1")) .setHostname("test") .build(); when(slaveAndRackHelper.getRackId(offer)).thenReturn(Optional.<String> absent()); when(slaveAndRackHelper.getMaybeTruncatedHost(offer)).thenReturn("host"); when(slaveAndRackHelper.getRackIdOrDefault(offer)).thenReturn("DEFAULT"); } @Test public void testShellCommand() { final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER).build(); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setCommand(Optional.of("/bin/echo hi")) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, null, taskRequest, taskResources, executorResources); assertEquals("/bin/echo hi", task.getMesosTask().getCommand().getValue()); assertEquals(0, task.getMesosTask().getCommand().getArgumentsCount()); assertTrue(task.getMesosTask().getCommand().getShell()); } @Test public void testJobUserPassedAsEnvironmentVariable() { final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER) .build(); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setCommand(Optional.of("/bin/echo hi")) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, null, taskRequest, taskResources, executorResources); List<Variable> environmentVariables = task.getMesosTask() .getCommand() .getEnvironment() .getVariablesList(); boolean success = false; for (Variable environmentVariable : environmentVariables) { success = success || (environmentVariable.getName().equals("STARTED_BY_USER") && environmentVariable.getValue().equals(user)); } assertTrue("Expected env variable STARTED_BY_USER to be set to " + user, success); } @Test public void testArgumentCommand() { final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER).build(); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setCommand(Optional.of("/bin/echo")) .setArguments(Optional.of(Collections.singletonList("wat"))) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, null, taskRequest, taskResources, executorResources); assertEquals("/bin/echo", task.getMesosTask().getCommand().getValue()); assertEquals(1, task.getMesosTask().getCommand().getArgumentsCount()); assertEquals("wat", task.getMesosTask().getCommand().getArguments(0)); assertFalse(task.getMesosTask().getCommand().getShell()); } @Test public void testDockerTask() { taskResources = new Resources(1, 1, 1, 0); final Protos.Resource portsResource = Protos.Resource.newBuilder() .setName("ports") .setType(Protos.Value.Type.RANGES) .setRanges(Protos.Value.Ranges.newBuilder() .addRange(Protos.Value.Range.newBuilder() .setBegin(31000) .setEnd(31000).build()).build()).build(); final SingularityDockerPortMapping literalMapping = new SingularityDockerPortMapping(Optional.<SingularityPortMappingType>absent(), 80, Optional.of(SingularityPortMappingType.LITERAL), 8080, Optional.<String>absent()); final SingularityDockerPortMapping offerMapping = new SingularityDockerPortMapping(Optional.<SingularityPortMappingType>absent(), 81, Optional.of(SingularityPortMappingType.FROM_OFFER), 0, Optional.of("udp")); final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER).build(); final SingularityContainerInfo containerInfo = new SingularityContainerInfo( SingularityContainerType.DOCKER, Optional.of(Arrays.asList( new SingularityVolume("/container", Optional.of("/host"), SingularityDockerVolumeMode.RW), new SingularityVolume("/container/${TASK_REQUEST_ID}/${TASK_DEPLOY_ID}", Optional.of("/host/${TASK_ID}"), SingularityDockerVolumeMode.RO))), Optional.of(new SingularityDockerInfo("docker-image", true, SingularityDockerNetworkType.BRIDGE, Optional.of(Arrays.asList(literalMapping, offerMapping)), Optional.of(false), Optional.<Map<String, String>>of( ImmutableMap.of("env", "var=value"))))); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setContainerInfo(Optional.of(containerInfo)) .setCommand(Optional.of("/bin/echo")) .setArguments(Optional.of(Collections.singletonList("wat"))) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, Collections.singletonList(portsResource), taskRequest, taskResources, executorResources); assertEquals("/bin/echo", task.getMesosTask().getCommand().getValue()); assertEquals(1, task.getMesosTask().getCommand().getArgumentsCount()); assertEquals("wat", task.getMesosTask().getCommand().getArguments(0)); assertFalse(task.getMesosTask().getCommand().getShell()); assertEquals(Type.DOCKER, task.getMesosTask().getContainer().getType()); assertEquals("docker-image", task.getMesosTask().getContainer().getDocker().getImage()); assertTrue(task.getMesosTask().getContainer().getDocker().getPrivileged()); assertEquals("/container", task.getMesosTask().getContainer().getVolumes(0).getContainerPath()); assertEquals("/host", task.getMesosTask().getContainer().getVolumes(0).getHostPath()); assertEquals(Mode.RW, task.getMesosTask().getContainer().getVolumes(0).getMode()); Parameter envParameter = Parameter.newBuilder().setKey("env").setValue("var=value").build(); assertTrue(task.getMesosTask().getContainer().getDocker().getParametersList().contains(envParameter)); assertEquals(String.format("/container/%s/%s", task.getTaskRequest().getDeploy().getRequestId(), task.getTaskRequest().getDeploy().getId()), task.getMesosTask().getContainer().getVolumes(1).getContainerPath()); assertEquals(String.format("/host/%s", task.getMesosTask().getTaskId().getValue()), task.getMesosTask().getContainer().getVolumes(1).getHostPath()); assertEquals(Mode.RO, task.getMesosTask().getContainer().getVolumes(1).getMode()); assertEquals(80, task.getMesosTask().getContainer().getDocker().getPortMappings(0).getContainerPort()); assertEquals(8080, task.getMesosTask().getContainer().getDocker().getPortMappings(0).getHostPort()); assertEquals("tcp", task.getMesosTask().getContainer().getDocker().getPortMappings(0).getProtocol()); assertTrue(MesosUtils.getAllPorts(task.getMesosTask().getResourcesList()).contains(8080L)); assertEquals(81, task.getMesosTask().getContainer().getDocker().getPortMappings(1).getContainerPort()); assertEquals(31000, task.getMesosTask().getContainer().getDocker().getPortMappings(1).getHostPort()); assertEquals("udp", task.getMesosTask().getContainer().getDocker().getPortMappings(1).getProtocol()); assertEquals(Protos.ContainerInfo.DockerInfo.Network.BRIDGE, task.getMesosTask().getContainer().getDocker().getNetwork()); } @Test public void testDockerMinimalNetworking() { taskResources = new Resources(1, 1, 0, 0); final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER).build(); final SingularityContainerInfo containerInfo = new SingularityContainerInfo( SingularityContainerType.DOCKER, Optional.<List<SingularityVolume>>absent(), Optional.of(new SingularityDockerInfo("docker-image", true, SingularityDockerNetworkType.NONE, Optional.<List<SingularityDockerPortMapping>>absent(), Optional.<Boolean>absent(), Optional.<Map<String, String>>absent()))); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setContainerInfo(Optional.of(containerInfo)) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, Collections.<Protos.Resource>emptyList(), taskRequest, taskResources, executorResources); assertEquals(Type.DOCKER, task.getMesosTask().getContainer().getType()); assertEquals(Protos.ContainerInfo.DockerInfo.Network.NONE, task.getMesosTask().getContainer().getDocker().getNetwork()); } @Test public void testAutomaticPortMapping() { NetworkConfiguration netConf = new NetworkConfiguration(); netConf.setDefaultPortMapping(true); configuration.setNetworkConfiguration(netConf); taskResources = new Resources(1, 1, 2); final SingularityRequest request = new SingularityRequestBuilder("test", RequestType.WORKER).build(); final SingularityContainerInfo containerInfo = new SingularityContainerInfo( SingularityContainerType.DOCKER, Optional.<List<SingularityVolume>>absent(), Optional.of(new SingularityDockerInfo("docker-image", false, SingularityDockerNetworkType.BRIDGE, Optional.<List<SingularityDockerPortMapping>>absent(), Optional.<Boolean>absent(), Optional.<Map<String, String>>absent()))); final SingularityDeploy deploy = new SingularityDeployBuilder("test", "1") .setContainerInfo(Optional.of(containerInfo)) .build(); final SingularityTaskRequest taskRequest = new SingularityTaskRequest(request, deploy, pendingTask); final SingularityTask task = builder.buildTask(offer, Collections.singletonList(MesosUtils.getPortRangeResource(31010, 31011)), taskRequest, taskResources, executorResources); assertEquals(Type.DOCKER, task.getMesosTask().getContainer().getType()); assertEquals(Protos.ContainerInfo.DockerInfo.Network.BRIDGE, task.getMesosTask().getContainer().getDocker().getNetwork()); List<PortMapping> portMappings = task.getMesosTask().getContainer().getDocker().getPortMappingsList(); assertEquals(2, portMappings.size()); assertEquals(31010, portMappings.get(0).getHostPort()); assertEquals(31010, portMappings.get(0).getContainerPort()); assertEquals(31011, portMappings.get(1).getHostPort()); assertEquals(31011, portMappings.get(1).getContainerPort()); } private static class CreateFakeId implements Answer<String> { private final AtomicLong string = new AtomicLong(); @Override public String answer(InvocationOnMock invocation) throws Throwable { return String.valueOf(string.incrementAndGet()); } } }