/** * 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.preemptor; import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.util.concurrent.AbstractScheduledService; import com.google.inject.AbstractModule; import com.google.inject.PrivateModule; import com.google.inject.TypeLiteral; import org.apache.aurora.common.args.Arg; import org.apache.aurora.common.args.CmdLine; import org.apache.aurora.common.args.constraints.Positive; import org.apache.aurora.common.quantity.Amount; import org.apache.aurora.common.quantity.Time; import org.apache.aurora.scheduler.SchedulerServicesModule; import org.apache.aurora.scheduler.base.TaskGroupKey; import org.apache.aurora.scheduler.events.PubsubEventModule; import org.apache.aurora.scheduler.preemptor.BiCache.BiCacheSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.util.Objects.requireNonNull; public class PreemptorModule extends AbstractModule { private static final Logger LOG = LoggerFactory.getLogger(PreemptorModule.class); @CmdLine(name = "enable_preemptor", help = "Enable the preemptor and preemption") private static final Arg<Boolean> ENABLE_PREEMPTOR = Arg.create(true); @CmdLine(name = "preemption_delay", help = "Time interval after which a pending task becomes eligible to preempt other tasks") private static final Arg<Amount<Long, Time>> PREEMPTION_DELAY = Arg.create(Amount.of(3L, Time.MINUTES)); @CmdLine(name = "preemption_slot_hold_time", help = "Time to hold a preemption slot found before it is discarded.") private static final Arg<Amount<Long, Time>> PREEMPTION_SLOT_HOLD_TIME = Arg.create(Amount.of(5L, Time.MINUTES)); @CmdLine(name = "preemption_slot_search_interval", help = "Time interval between pending task preemption slot searches.") private static final Arg<Amount<Long, Time>> PREEMPTION_SLOT_SEARCH_INTERVAL = Arg.create(Amount.of(1L, Time.MINUTES)); @Positive @CmdLine(name = "preemption_reservation_max_batch_size", help = "The maximum number of reservations for a task group to be made in a batch.") private static final Arg<Integer> RESERVATION_MAX_BATCH_SIZE = Arg.create(5); private final boolean enablePreemptor; private final Amount<Long, Time> preemptionDelay; private final Amount<Long, Time> slotSearchInterval; private final Integer reservationBatchSize; @VisibleForTesting public PreemptorModule( boolean enablePreemptor, Amount<Long, Time> preemptionDelay, Amount<Long, Time> slotSearchInterval, Integer reservationBatchSize) { this.enablePreemptor = enablePreemptor; this.preemptionDelay = requireNonNull(preemptionDelay); this.slotSearchInterval = requireNonNull(slotSearchInterval); this.reservationBatchSize = requireNonNull(reservationBatchSize); } public PreemptorModule() { this( ENABLE_PREEMPTOR.get(), PREEMPTION_DELAY.get(), PREEMPTION_SLOT_SEARCH_INTERVAL.get(), RESERVATION_MAX_BATCH_SIZE.get()); } @Override protected void configure() { install(new PrivateModule() { @Override protected void configure() { if (enablePreemptor) { LOG.info("Preemptor Enabled."); bind(PreemptorMetrics.class).in(Singleton.class); bind(PreemptionVictimFilter.class) .to(PreemptionVictimFilter.PreemptionVictimFilterImpl.class); bind(PreemptionVictimFilter.PreemptionVictimFilterImpl.class).in(Singleton.class); bind(Preemptor.class).to(Preemptor.PreemptorImpl.class); bind(Preemptor.PreemptorImpl.class).in(Singleton.class); bind(new TypeLiteral<Amount<Long, Time>>() { }) .annotatedWith(PendingTaskProcessor.PreemptionDelay.class) .toInstance(preemptionDelay); bind(BiCacheSettings.class).toInstance( new BiCacheSettings(PREEMPTION_SLOT_HOLD_TIME.get(), "preemption_slot")); bind(new TypeLiteral<BiCache<PreemptionProposal, TaskGroupKey>>() { }) .in(Singleton.class); bind(new TypeLiteral<Integer>() { }) .annotatedWith(PendingTaskProcessor.ReservationBatchSize.class) .toInstance(reservationBatchSize); bind(PendingTaskProcessor.class).in(Singleton.class); bind(ClusterState.class).to(ClusterStateImpl.class); bind(ClusterStateImpl.class).in(Singleton.class); expose(ClusterStateImpl.class); bind(PreemptorService.class).in(Singleton.class); bind(AbstractScheduledService.Scheduler.class).toInstance( AbstractScheduledService.Scheduler.newFixedRateSchedule( 0L, slotSearchInterval.getValue(), slotSearchInterval.getUnit().getTimeUnit())); expose(PreemptorService.class); expose(PendingTaskProcessor.class); } else { bind(Preemptor.class).toInstance(NULL_PREEMPTOR); LOG.warn("Preemptor Disabled."); } expose(Preemptor.class); } }); // We can't do this in the private module due to the known conflict between multibindings // and private modules due to multiple injectors. We accept the added complexity here to keep // the other bindings private. PubsubEventModule.bindSubscriber(binder(), ClusterStateImpl.class); if (enablePreemptor) { SchedulerServicesModule.addSchedulerActiveServiceBinding(binder()) .to(PreemptorService.class); } } static class PreemptorService extends AbstractScheduledService { private final PendingTaskProcessor slotFinder; private final Scheduler schedule; @Inject PreemptorService(PendingTaskProcessor slotFinder, Scheduler schedule) { this.slotFinder = requireNonNull(slotFinder); this.schedule = requireNonNull(schedule); } @Override protected void runOneIteration() { slotFinder.run(); } @Override protected Scheduler scheduler() { return schedule; } } private static final Preemptor NULL_PREEMPTOR = (task, jobState, storeProvider) -> Optional.absent(); }