/** * 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.updater; import java.util.Set; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.inject.Inject; import org.apache.aurora.common.quantity.Amount; import org.apache.aurora.common.quantity.Time; import org.apache.aurora.common.util.Clock; import org.apache.aurora.gen.JobUpdateStatus; import org.apache.aurora.scheduler.storage.entities.IInstanceTaskConfig; import org.apache.aurora.scheduler.storage.entities.IJobUpdateInstructions; import org.apache.aurora.scheduler.storage.entities.IJobUpdateSettings; import org.apache.aurora.scheduler.storage.entities.IRange; import org.apache.aurora.scheduler.storage.entities.IScheduledTask; import org.apache.aurora.scheduler.storage.entities.ITaskConfig; import org.apache.aurora.scheduler.updater.strategy.BatchStrategy; import org.apache.aurora.scheduler.updater.strategy.QueueStrategy; import org.apache.aurora.scheduler.updater.strategy.UpdateStrategy; import static java.util.Objects.requireNonNull; import static com.google.common.base.Preconditions.checkArgument; import static org.apache.aurora.scheduler.base.Numbers.toRange; /** * A factory that produces job updaters based on a job update configuration. * <p> * TODO(wfarner): Use AssistedInject to inject this (github.com/google/guice/wiki/AssistedInject). */ interface UpdateFactory { /** * Creates a one-way job updater that will execute the job update configuration in the direction * specified by {@code rollingForward}. * * @param configuration Configuration to act on. * @param rollingForward {@code true} if this is a job update, {@code false} if it is a rollback. * @return An updater that will execute the job update as specified in the * {@code configuration}. */ Update newUpdate( IJobUpdateInstructions configuration, boolean rollingForward); class UpdateFactoryImpl implements UpdateFactory { private final Clock clock; @Inject UpdateFactoryImpl(Clock clock) { this.clock = requireNonNull(clock); } @Override public Update newUpdate(IJobUpdateInstructions instructions, boolean rollingForward) { requireNonNull(instructions); IJobUpdateSettings settings = instructions.getSettings(); checkArgument( settings.getMinWaitInInstanceRunningMs() >= 0, "Min wait in running must be non-negative."); checkArgument( settings.getUpdateGroupSize() > 0, "Update group size must be positive."); Set<Integer> desiredInstances = instructions.isSetDesiredState() ? expandInstanceIds(ImmutableSet.of(instructions.getDesiredState())) : ImmutableSet.of(); Set<Integer> instances = ImmutableSet.copyOf( Sets.union(expandInstanceIds(instructions.getInitialState()), desiredInstances)); ImmutableMap.Builder<Integer, StateEvaluator<Optional<IScheduledTask>>> evaluators = ImmutableMap.builder(); for (int instanceId : instances) { Optional<ITaskConfig> desiredStateConfig; if (rollingForward) { desiredStateConfig = desiredInstances.contains(instanceId) ? Optional.of(instructions.getDesiredState().getTask()) : Optional.absent(); } else { desiredStateConfig = getConfig(instanceId, instructions.getInitialState()); } evaluators.put( instanceId, new InstanceUpdater( desiredStateConfig, settings.getMaxPerInstanceFailures(), Amount.of((long) settings.getMinWaitInInstanceRunningMs(), Time.MILLISECONDS), clock)); } Ordering<Integer> updateOrder = rollingForward ? Ordering.natural() : Ordering.natural().reverse(); UpdateStrategy<Integer> strategy = settings.isWaitForBatchCompletion() ? new BatchStrategy<>(updateOrder, settings.getUpdateGroupSize()) : new QueueStrategy<>(updateOrder, settings.getUpdateGroupSize()); JobUpdateStatus successStatus = rollingForward ? JobUpdateStatus.ROLLED_FORWARD : JobUpdateStatus.ROLLED_BACK; JobUpdateStatus failureStatus = rollingForward && settings.isRollbackOnFailure() ? JobUpdateStatus.ROLLING_BACK : JobUpdateStatus.FAILED; return new Update( new OneWayJobUpdater<>( strategy, settings.getMaxFailedInstances(), evaluators.build()), successStatus, failureStatus); } @VisibleForTesting static Set<Integer> expandInstanceIds(Set<IInstanceTaskConfig> instanceGroups) { return Updates.getInstanceIds(instanceGroups).asSet(DiscreteDomain.integers()); } private static Optional<ITaskConfig> getConfig( int id, Set<IInstanceTaskConfig> instanceGroups) { for (IInstanceTaskConfig group : instanceGroups) { for (IRange range : group.getInstances()) { if (toRange(range).contains(id)) { return Optional.of(group.getTask()); } } } return Optional.absent(); } } class Update { private final OneWayJobUpdater<Integer, Optional<IScheduledTask>> updater; private final JobUpdateStatus successStatus; private final JobUpdateStatus failureStatus; Update( OneWayJobUpdater<Integer, Optional<IScheduledTask>> updater, JobUpdateStatus successStatus, JobUpdateStatus failureStatus) { this.updater = requireNonNull(updater); this.successStatus = requireNonNull(successStatus); this.failureStatus = requireNonNull(failureStatus); } OneWayJobUpdater<Integer, Optional<IScheduledTask>> getUpdater() { return updater; } JobUpdateStatus getSuccessStatus() { return successStatus; } JobUpdateStatus getFailureStatus() { return failureStatus; } } }