/** * 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.filter; import java.time.Instant; import java.util.Comparator; import java.util.EnumSet; import java.util.Set; import javax.inject.Inject; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import org.apache.aurora.common.inject.TimedInterceptor.Timed; 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.MaintenanceMode; import org.apache.aurora.gen.TaskConstraint; import org.apache.aurora.scheduler.configuration.ConfigurationManager; import org.apache.aurora.scheduler.offers.OffersModule.UnavailabilityThreshold; import org.apache.aurora.scheduler.resources.ResourceBag; import org.apache.aurora.scheduler.resources.ResourceType; import org.apache.aurora.scheduler.storage.entities.IAttribute; import org.apache.aurora.scheduler.storage.entities.IConstraint; import org.apache.aurora.scheduler.storage.entities.IHostAttributes; import static java.util.Objects.requireNonNull; import static org.apache.aurora.gen.MaintenanceMode.DRAINED; import static org.apache.aurora.gen.MaintenanceMode.DRAINING; import static org.apache.aurora.scheduler.configuration.ConfigurationManager.DEDICATED_ATTRIBUTE; /** * Implementation of the scheduling filter that ensures resource requirements of tasks are * fulfilled, and that tasks are allowed to run on the given machine. */ public class SchedulingFilterImpl implements SchedulingFilter { private final Amount<Long, Time> unavailabilityThreshold; private final Clock clock; @Inject public SchedulingFilterImpl(@UnavailabilityThreshold Amount<Long, Time> threshold, Clock clock) { this.unavailabilityThreshold = requireNonNull(threshold); this.clock = requireNonNull(clock); } private static final Set<MaintenanceMode> VETO_MODES = EnumSet.of(DRAINING, DRAINED); @VisibleForTesting static int scale(double value, int range) { return Math.min( VetoType.INSUFFICIENT_RESOURCES.getScore(), (int) (VetoType.INSUFFICIENT_RESOURCES.getScore() * value) / range); } private static void maybeAddVeto( ImmutableSet.Builder<Veto> vetoes, ResourceType resourceType, double available, double requested) { double tooLarge = requested - available; if (tooLarge > 0) { vetoes.add(Veto.insufficientResources( resourceType.getAuroraName(), scale(tooLarge, resourceType.getScalingRange()))); } } private static Set<Veto> getResourceVetoes(ResourceBag available, ResourceBag required) { ImmutableSet.Builder<Veto> vetoes = ImmutableSet.builder(); required.streamResourceVectors().forEach( e -> maybeAddVeto(vetoes, e.getKey(), available.valueOf(e.getKey()), e.getValue())); return vetoes.build(); } private static boolean isValueConstraint(IConstraint constraint) { return constraint.getConstraint().getSetField() == TaskConstraint._Fields.VALUE; } private static final Ordering<IConstraint> VALUES_FIRST = Ordering.from( new Comparator<IConstraint>() { @Override public int compare(IConstraint a, IConstraint b) { if (a.getConstraint().getSetField() == b.getConstraint().getSetField()) { return 0; } return isValueConstraint(a) ? -1 : 1; } }); private Optional<Veto> getConstraintVeto( Iterable<IConstraint> taskConstraints, AttributeAggregate jobState, Iterable<IAttribute> offerAttributes) { for (IConstraint constraint : VALUES_FIRST.sortedCopy(taskConstraints)) { Optional<Veto> veto = ConstraintMatcher.getVeto(jobState, offerAttributes, constraint); if (veto.isPresent()) { // Break early to avoid potentially-expensive operations to satisfy other constraints. return veto; } } return Optional.absent(); } private Optional<Veto> getAuroraMaintenanceVeto(MaintenanceMode mode) { return VETO_MODES.contains(mode) ? Optional.of(Veto.maintenance(mode.toString().toLowerCase())) : Optional.absent(); } private Optional<Veto> getMesosMaintenanceVeto(Optional<Instant> unavailabilityStart) { if (unavailabilityStart.isPresent()) { Instant start = unavailabilityStart.get(); Instant drainTime = start.minusMillis(unavailabilityThreshold.as(Time.MILLISECONDS)); if (clock.nowInstant().isAfter(drainTime)) { return Optional.of(Veto.maintenance(DRAINING.toString().toLowerCase())); } } return Optional.absent(); } private boolean isDedicated(IHostAttributes attributes) { return Iterables.any( attributes.getAttributes(), new ConstraintMatcher.NameFilter(DEDICATED_ATTRIBUTE)); } @Timed("scheduling_filter") @Override public Set<Veto> filter(UnusedResource resource, ResourceRequest request) { // Apply veto filtering rules from higher to lower score making sure we cut over and return // early any time a veto from a score group is applied. This helps to more accurately report // a veto reason in the NearestFit. // 1. Dedicated constraint check (highest score). if (!ConfigurationManager.isDedicated(request.getConstraints()) && isDedicated(resource.getAttributes())) { return ImmutableSet.of(Veto.dedicatedHostConstraintMismatch()); } // 2. Host maintenance check. Optional<Veto> maintenanceVeto = getAuroraMaintenanceVeto(resource.getAttributes().getMode()); if (maintenanceVeto.isPresent()) { return maintenanceVeto.asSet(); } Optional<Veto> mesosMaintenanceVeto = getMesosMaintenanceVeto(resource.getUnavailabilityStart()); if (mesosMaintenanceVeto.isPresent()) { return mesosMaintenanceVeto.asSet(); } // 3. Value and limit constraint check. Optional<Veto> constraintVeto = getConstraintVeto( request.getConstraints(), request.getJobState(), resource.getAttributes().getAttributes()); if (constraintVeto.isPresent()) { return constraintVeto.asSet(); } // 4. Resource check (lowest score). return getResourceVetoes(resource.getResourceBag(), request.getResourceBag()); } }