blob: 8709137e83e4a4d5c934d0c3bafc9a1313d7b86a [file] [log] [blame]
/**
* Licensed 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.apache.aurora.scheduler.configuration;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.aurora.gen.AppcImage;
import org.apache.aurora.gen.Constraint;
import org.apache.aurora.gen.Container;
import org.apache.aurora.gen.CoordinatorSlaPolicy;
import org.apache.aurora.gen.CountSlaPolicy;
import org.apache.aurora.gen.CronCollisionPolicy;
import org.apache.aurora.gen.DockerParameter;
import org.apache.aurora.gen.ExecutorConfig;
import org.apache.aurora.gen.Identity;
import org.apache.aurora.gen.Image;
import org.apache.aurora.gen.JobConfiguration;
import org.apache.aurora.gen.JobKey;
import org.apache.aurora.gen.LimitConstraint;
import org.apache.aurora.gen.MesosContainer;
import org.apache.aurora.gen.MesosFetcherURI;
import org.apache.aurora.gen.Mode;
import org.apache.aurora.gen.PercentageSlaPolicy;
import org.apache.aurora.gen.SlaPolicy;
import org.apache.aurora.gen.TaskConfig;
import org.apache.aurora.gen.TaskConstraint;
import org.apache.aurora.gen.ValueConstraint;
import org.apache.aurora.gen.Volume;
import org.apache.aurora.gen.apiConstants;
import org.apache.aurora.scheduler.base.JobKeys;
import org.apache.aurora.scheduler.base.TaskTestUtil;
import org.apache.aurora.scheduler.configuration.ConfigurationManager.ConfigurationManagerSettings;
import org.apache.aurora.scheduler.configuration.ConfigurationManager.TaskDescriptionException;
import org.apache.aurora.scheduler.mesos.TestExecutorSettings;
import org.apache.aurora.scheduler.storage.entities.IDockerParameter;
import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.apache.aurora.gen.Resource.diskMb;
import static org.apache.aurora.gen.Resource.namedPort;
import static org.apache.aurora.gen.Resource.numCpus;
import static org.apache.aurora.gen.Resource.numGpus;
import static org.apache.aurora.gen.Resource.ramMb;
import static org.apache.aurora.gen.test.testConstants.INVALID_IDENTIFIERS;
import static org.apache.aurora.gen.test.testConstants.VALID_IDENTIFIERS;
import static org.apache.aurora.scheduler.base.UserProvidedStrings.isGoodIdentifier;
import static org.apache.aurora.scheduler.configuration.ConfigurationManager.DEDICATED_ATTRIBUTE;
import static org.apache.aurora.scheduler.configuration.ConfigurationManager.NO_CONTAINER_VOLUMES;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
// TODO(kevints): Improve test coverage for this class.
public class ConfigurationManagerTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
private static final ImmutableSet<Container._Fields> ALL_CONTAINER_TYPES =
ImmutableSet.copyOf(Container._Fields.values());
private static final int MIN_REQUIRED_INSTANCES = 20;
private static final long MAX_SLA_DURATION_SECS = 7200;
private static final long DEFAULT_SLA_PERCENTAGE = 95;
private static final JobKey JOB_KEY = new JobKey("owner-role", "devel", "email_stats");
private static final JobConfiguration UNSANITIZED_JOB_CONFIGURATION = new JobConfiguration()
.setKey(JOB_KEY)
.setCronSchedule("0 2 * * *")
.setCronCollisionPolicy(CronCollisionPolicy.KILL_EXISTING)
.setInstanceCount(1)
.setTaskConfig(
new TaskConfig()
.setJob(JOB_KEY)
.setIsService(false)
.setTaskLinks(ImmutableMap.of())
.setExecutorConfig(new ExecutorConfig(apiConstants.AURORA_EXECUTOR_NAME, "config"))
.setPriority(0)
.setOwner(null)
.setContactEmail("foo@twitter.com")
.setProduction(false)
.setMetadata(null)
.setMaxTaskFailures(0)
.setConstraints(
ImmutableSet.of(
new Constraint()
.setName("executor")
.setConstraint(TaskConstraint
.value(new ValueConstraint()
.setNegated(false)
.setValues(ImmutableSet.of("legacy")))),
new Constraint()
.setName("host")
.setConstraint(TaskConstraint.limit(new LimitConstraint()
.setLimit(1))),
new Constraint()
.setName(DEDICATED_ATTRIBUTE)
.setConstraint(TaskConstraint.value(new ValueConstraint(
false, ImmutableSet.of("foo"))))))
.setOwner(new Identity().setUser("owner-user"))
.setResources(ImmutableSet.of(
numCpus(1.0),
ramMb(1),
diskMb(1))));
private static final ITaskConfig CONFIG_WITH_CONTAINER =
TaskTestUtil.makeConfig(JobKeys.from("role", "env", "job"));
private static final ConfigurationManager CONFIGURATION_MANAGER = new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
false,
ImmutableList.of(),
true,
false,
true,
false,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
ConfigurationManager.DEFAULT_ALLOWED_JOB_ENVIRONMENTS,
false),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR);
private static final ConfigurationManager DOCKER_CONFIGURATION_MANAGER = new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
true,
ImmutableList.of(new DockerParameter("foo", "bar")),
false,
true,
true,
true,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
ConfigurationManager.DEFAULT_ALLOWED_JOB_ENVIRONMENTS,
false),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR);
@Test
public void testIsGoodIdentifier() {
for (String identifier : VALID_IDENTIFIERS) {
assertTrue(isGoodIdentifier(identifier));
}
for (String identifier : INVALID_IDENTIFIERS) {
assertFalse(isGoodIdentifier(identifier));
}
}
@Test
public void testBadContainerConfig() throws TaskDescriptionException {
TaskConfig taskConfig = CONFIG_WITH_CONTAINER.newBuilder();
taskConfig.getContainer().getDocker().setImage(null);
expectTaskDescriptionException("A container must specify an image");
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(taskConfig), 100);
}
@Test
public void testDisallowedDockerParameters() throws TaskDescriptionException {
TaskConfig taskConfig = CONFIG_WITH_CONTAINER.newBuilder();
taskConfig.getContainer().getDocker().addToParameters(new DockerParameter("foo", "bar"));
expectTaskDescriptionException(ConfigurationManager.NO_DOCKER_PARAMETERS);
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(taskConfig), 100);
}
@Test
public void testDisallowNoExecutorDockerTask() throws TaskDescriptionException {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.getContainer().getDocker().unsetParameters();
builder.unsetExecutorConfig();
expectTaskDescriptionException(ConfigurationManager.EXECUTOR_REQUIRED_WITH_DOCKER);
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 1);
}
@Test
public void testAllowNoExecutorDockerTask() throws TaskDescriptionException {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.unsetExecutorConfig();
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testInvalidTier() throws TaskDescriptionException {
ITaskConfig config = ITaskConfig.build(UNSANITIZED_JOB_CONFIGURATION.deepCopy().getTaskConfig()
.setTier("pr/d"));
expectTaskDescriptionException("Tier contains illegal characters");
CONFIGURATION_MANAGER.validateAndPopulate(config, 100);
}
@Test
public void testDefaultDockerParameters() throws TaskDescriptionException {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.getContainer().getDocker().setParameters(ImmutableList.of());
ITaskConfig result =
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
// The resulting task config should contain parameters supplied to the ConfigurationManager.
List<IDockerParameter> params = result.getContainer().getDocker().getParameters();
assertThat(
params, is(ImmutableList.of(IDockerParameter.build(new DockerParameter("foo", "bar")))));
}
@Test
public void testPassthroughDockerParameters() throws TaskDescriptionException {
TaskConfig taskConfig = CONFIG_WITH_CONTAINER.newBuilder();
DockerParameter userParameter = new DockerParameter("bar", "baz");
taskConfig.getContainer().getDocker().getParameters().clear();
taskConfig.getContainer().getDocker().addToParameters(userParameter);
ITaskConfig result = DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
ITaskConfig.build(taskConfig), 100);
// The resulting task config should contain parameters supplied from user config.
List<IDockerParameter> params = result.getContainer().getDocker().getParameters();
assertThat(params, is(ImmutableList.of(IDockerParameter.build(userParameter))));
}
@Test
public void testExclusiveDedicatedRoleAllowed() throws Exception {
TaskConfig builder = UNSANITIZED_JOB_CONFIGURATION.deepCopy().getTaskConfig();
builder.setConstraints(ImmutableSet.of(new Constraint()
.setName(DEDICATED_ATTRIBUTE)
.setConstraint(TaskConstraint.value(
new ValueConstraint(false, ImmutableSet.of(JOB_KEY.getRole() + "/f"))))));
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testNonExclusiveDedicatedAllowed() throws Exception {
TaskConfig builder = UNSANITIZED_JOB_CONFIGURATION.deepCopy().getTaskConfig();
builder.setConstraints(ImmutableSet.of(new Constraint()
.setName(DEDICATED_ATTRIBUTE)
.setConstraint(TaskConstraint.value(new ValueConstraint(false, ImmutableSet.of("*/f"))))));
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testExclusiveDedicatedRoleMismatch() throws Exception {
TaskConfig builder = UNSANITIZED_JOB_CONFIGURATION.deepCopy().getTaskConfig();
builder.setConstraints(ImmutableSet.of(new Constraint()
.setName(DEDICATED_ATTRIBUTE)
.setConstraint(TaskConstraint.value(new ValueConstraint(false, ImmutableSet.of("r/f"))))));
expectTaskDescriptionException("Only r may use hosts dedicated for that role.");
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testMultipleResourceValuesBlocked() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.addToResources(numCpus(3.0));
builder.addToResources(ramMb(72));
expectTaskDescriptionException("Multiple resource values are not supported for CPU, RAM");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testMultipleResourceValuesAllowed() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.addToResources(namedPort("thrift"));
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testGpuResourcesNotAllowed() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.addToResources(numGpus(2));
expectTaskDescriptionException("GPU resource support is disabled in this cluster.");
new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
true,
ImmutableList.of(new DockerParameter("foo", "bar")),
false,
false,
false,
false,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
ConfigurationManager.DEFAULT_ALLOWED_JOB_ENVIRONMENTS,
false),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR).validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testMesosFetcherDisabled() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.setMesosFetcherUris(
ImmutableSet.of(
new MesosFetcherURI("pathA").setExtract(true).setCache(true),
new MesosFetcherURI("pathB").setExtract(true).setCache(true)));
expectTaskDescriptionException(ConfigurationManager.MESOS_FETCHER_DISABLED);
new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
true,
ImmutableList.of(new DockerParameter("foo", "bar")),
false,
false,
false,
false,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
".+",
false),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR)
.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testContainerVolumesDisabled() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
MesosContainer container = new MesosContainer()
.setImage(Image.appc(new AppcImage("name", "id")))
.setVolumes(ImmutableList.of(new Volume("container", "host", Mode.RO)));
builder.setContainer(Container.mesos(container));
expectTaskDescriptionException(NO_CONTAINER_VOLUMES);
CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
}
@Test
public void testTaskLinks() throws Exception {
TaskConfig builder = CONFIG_WITH_CONTAINER.newBuilder();
builder.addToResources(namedPort("health"));
builder.unsetTaskLinks();
ITaskConfig populated =
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(ITaskConfig.build(builder), 100);
assertEquals(ImmutableSet.of("health", "http"), populated.getTaskLinks().keySet());
}
@Test
public void testJobEnvironmentValidation() throws Exception {
JobConfiguration jobConfiguration = UNSANITIZED_JOB_CONFIGURATION.deepCopy();
jobConfiguration.getKey().setEnvironment("foo");
expectTaskDescriptionException("Job environment foo doesn't match: b.r");
new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
true,
ImmutableList.of(new DockerParameter("foo", "bar")),
false,
true,
true,
true,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
"b.r",
false),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR)
.validateAndPopulate(IJobConfiguration.build(jobConfiguration));
}
@Test
public void testSlaPolicyNonProdTiersEnabled() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setTier(TaskTestUtil.DEV_TIER_NAME)
.setSlaPolicy(SlaPolicy.countSlaPolicy(
new CountSlaPolicy()
.setCount(MIN_REQUIRED_INSTANCES - 1))));
new ConfigurationManager(
new ConfigurationManagerSettings(
ALL_CONTAINER_TYPES,
true,
ImmutableList.of(new DockerParameter("foo", "bar")),
false,
true,
true,
true,
MIN_REQUIRED_INSTANCES,
MAX_SLA_DURATION_SECS,
ConfigurationManager.DEFAULT_ALLOWED_JOB_ENVIRONMENTS,
true),
TaskTestUtil.TIER_MANAGER,
TaskTestUtil.THRIFT_BACKFILL,
TestExecutorSettings.THERMOS_EXECUTOR).validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testCountSlaPolicyUnsupportedTier() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setTier(TaskTestUtil.DEV_TIER_NAME)
.setSlaPolicy(SlaPolicy.countSlaPolicy(
new CountSlaPolicy()
.setCount(MIN_REQUIRED_INSTANCES))));
expectTaskDescriptionException("Tier 'tier-dev' does not support SlaPolicy.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testCountSlaPolicyTooFewInstances() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.countSlaPolicy(
new CountSlaPolicy()
.setCount(MIN_REQUIRED_INSTANCES))));
expectTaskDescriptionException(
"Job with fewer than 20 instances cannot have Percentage/Count SlaPolicy.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES - 1);
}
@Test
public void testCountSlaPolicyCountTooHigh() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.countSlaPolicy(
new CountSlaPolicy()
.setCount(MIN_REQUIRED_INSTANCES))));
expectTaskDescriptionException(
"Current CountSlaPolicy: count=20 will not allow any instances to be killed. "
+ "Must be less than instanceCount=20.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testCountSlaPolicyDurationSecsTooHigh() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.countSlaPolicy(
new CountSlaPolicy()
.setDurationSecs(MAX_SLA_DURATION_SECS + 1))));
expectTaskDescriptionException("CountSlaPolicy: durationSecs=7201 must be less than "
+ "cluster-wide maximum of 7200 secs.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testPercentageSlaPolicyUnsupportedTier() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setTier(TaskTestUtil.DEV_TIER_NAME)
.setSlaPolicy(SlaPolicy.percentageSlaPolicy(
new PercentageSlaPolicy()
.setPercentage(DEFAULT_SLA_PERCENTAGE))));
expectTaskDescriptionException("Tier 'tier-dev' does not support SlaPolicy.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testPercentageSlaPolicyTooFewInstances() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.percentageSlaPolicy(
new PercentageSlaPolicy()
.setPercentage(DEFAULT_SLA_PERCENTAGE))));
expectTaskDescriptionException(
"Job with fewer than 20 instances cannot have Percentage/Count SlaPolicy.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES - 1);
}
@Test
public void testPercentageSlaPolicyPercentageTooHigh() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.percentageSlaPolicy(
new PercentageSlaPolicy()
.setPercentage(100))));
expectTaskDescriptionException(
"Current PercentageSlaPolicy: percentage=100.000000 will not allow any instances "
+ "to be killed. Must be less than 95.000000.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testPercentageSlaPolicyDurationSecsTooHigh() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.percentageSlaPolicy(
new PercentageSlaPolicy()
.setDurationSecs(MAX_SLA_DURATION_SECS + 1))));
expectTaskDescriptionException("PercentageSlaPolicy: durationSecs=7201 must be less than "
+ "cluster-wide maximum of 7200 secs.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testCoordinatorSlaPolicyUnsupportedTier() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setTier(TaskTestUtil.DEV_TIER_NAME)
.setSlaPolicy(SlaPolicy.coordinatorSlaPolicy(
new CoordinatorSlaPolicy()
.setCoordinatorUrl("http://localhost"))));
expectTaskDescriptionException("Tier 'tier-dev' does not support SlaPolicy.");
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES);
}
@Test
public void testCoordinatorSlaPolicyTooFewInstances() throws Exception {
ITaskConfig config = ITaskConfig.build(
CONFIG_WITH_CONTAINER
.newBuilder()
.setSlaPolicy(SlaPolicy.coordinatorSlaPolicy(
new CoordinatorSlaPolicy()
.setCoordinatorUrl("http://localhost"))));
DOCKER_CONFIGURATION_MANAGER.validateAndPopulate(
config,
MIN_REQUIRED_INSTANCES - 1);
}
private void expectTaskDescriptionException(String message) {
expectedException.expect(TaskDescriptionException.class);
expectedException.expectMessage(message);
}
}