/**
* 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.Objects;
import java.util.Set;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import org.apache.aurora.scheduler.resources.ResourceBag;
import org.apache.aurora.scheduler.storage.entities.IConstraint;
import org.apache.aurora.scheduler.storage.entities.IHostAttributes;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
import static org.apache.aurora.scheduler.filter.SchedulingFilter.VetoType.CONSTRAINT_MISMATCH;
import static org.apache.aurora.scheduler.filter.SchedulingFilter.VetoType.INSUFFICIENT_RESOURCES;
import static org.apache.aurora.scheduler.filter.SchedulingFilter.VetoType.LIMIT_NOT_SATISFIED;
import static org.apache.aurora.scheduler.filter.SchedulingFilter.VetoType.MAINTENANCE;
/**
* Determines whether a proposed scheduling assignment should be allowed.
*/
public interface SchedulingFilter {
enum VetoGroup {
/**
* An empty group of {@link Veto} instances.
*/
EMPTY,
/**
* Represents a group of static {@link Veto} instances. Vetoes are considered static if
* a given offer will never be able to satisfy given task requirements.
*/
STATIC,
/**
* Represents a group of dynamic {@link Veto} instances. Vetoes are considered dynamic if
* a given offer may be able to satisfy given task requirements if cluster conditions change
* (e.g. other tasks are killed or rescheduled).
*/
DYNAMIC,
/**
* Represents a group of both static and dynamic {@link Veto} instances.
*/
MIXED
}
enum VetoType {
/**
* Not enough resources to satisfy a proposed scheduling assignment.
*/
INSUFFICIENT_RESOURCES("Insufficient: %s", VetoGroup.STATIC, (int) Math.pow(10, 3)),
/**
* Unable to satisfy proposed scheduler assignment constraints.
*/
CONSTRAINT_MISMATCH("Constraint not satisfied: %s", VetoGroup.STATIC, (int) Math.pow(10, 4)),
/**
* Constraint limit is not satisfied for a proposed scheduling assignment.
*/
LIMIT_NOT_SATISFIED("Limit not satisfied: %s", VetoGroup.DYNAMIC, (int) Math.pow(10, 4)),
/**
* Unable to satisfy a proposed scheduler assignment due to cluster maintenance.
*/
MAINTENANCE("Host %s for maintenance", VetoGroup.STATIC, (int) Math.pow(10, 5)),
/**
* Unable to satisfy proposed scheduler assignment dedicated host constraint.
*/
DEDICATED_CONSTRAINT_MISMATCH("Host is dedicated", VetoGroup.STATIC, (int) Math.pow(10, 6));
private final String reasonFormat;
private final VetoGroup group;
private final int score;
VetoType(String reasonFormat, VetoGroup group, int score) {
this.reasonFormat = reasonFormat;
this.group = group;
// Score ranges are chosen under assumption that no more than 9 same group score vetoes
// are applied per task/offer evaluation (e.g. 9 insufficient resource vetoes < 1 value
// constraint mismatch). This is a fair assumption given current "break early" constraint
// evaluation rules and a limited number of resource types (4). Should this ever change,
// consider more spread between score groups to ensure correct NearestFit operation.
this.score = score;
}
String formatReason(String reason) {
return String.format(reasonFormat, reason);
}
VetoGroup getGroup() {
return group;
}
int getScore() {
return score;
}
}
/**
* Reason for a proposed scheduling assignment to be filtered out.
* A veto also contains a score, which is an opaque indicator as to how strong a veto is. This
* is only intended to be used for relative ranking of vetoes for determining which veto against
* a scheduling assignment is 'weakest'.
*/
final class Veto {
private final VetoType vetoType;
private final String reason;
private final int score;
private Veto(VetoType vetoType, String reasonParameter, int score) {
this.vetoType = vetoType;
this.reason = vetoType.formatReason(reasonParameter);
this.score = score;
}
/**
* Creates a veto that represents an dedicated host constraint mismatch between the server
* and task's configuration for an attribute.
*
* @return A dedicated host constraint mismatch veto.
*/
public static Veto dedicatedHostConstraintMismatch() {
return new Veto(
VetoType.DEDICATED_CONSTRAINT_MISMATCH,
"",
VetoType.DEDICATED_CONSTRAINT_MISMATCH.getScore());
}
/**
* Creates a veto that represents a mismatch between the server and task's configuration
* for an attribute.
*
* @param constraint A constraint name.
* @return A constraint mismatch veto.
*/
public static Veto constraintMismatch(String constraint) {
return new Veto(CONSTRAINT_MISMATCH, constraint, CONSTRAINT_MISMATCH.getScore());
}
/**
* Creates a veto the represents an unsatisfied constraint limit between the server and task's
* configuration for an attribute.
*
* @param limit A constraint name.
* @return A unsatisfied limit veto.
*/
public static Veto unsatisfiedLimit(String limit) {
return new Veto(LIMIT_NOT_SATISFIED, limit, LIMIT_NOT_SATISFIED.getScore());
}
/**
* Creates a veto that represents an inability of the server to satisfy task's configuration
* resource requirements.
*
* @param resource A resource name.
* @param score A veto score.
* @return An insufficient resources veto.
*/
public static Veto insufficientResources(String resource, int score) {
return new Veto(INSUFFICIENT_RESOURCES, resource, score);
}
/**
* Creates a veto that represents a lack of suitable for assignment hosts due to cluster
* maintenance.
*
* @param maintenanceMode A maintenance mode.
* @return A maintenance veto.
*/
public static Veto maintenance(String maintenanceMode) {
return new Veto(MAINTENANCE, maintenanceMode, MAINTENANCE.getScore());
}
public String getReason() {
return reason;
}
public int getScore() {
return score;
}
public VetoType getVetoType() {
return vetoType;
}
public static VetoGroup identifyGroup(Iterable<Veto> vetoes) {
// This code has high call frequency and is optimized for reduced heap churn.
VetoGroup group = VetoGroup.EMPTY;
for (Veto veto : vetoes) {
VetoGroup currentGroup = veto.getVetoType().getGroup();
if (group == VetoGroup.EMPTY) {
group = currentGroup;
} else if (!currentGroup.equals(group)) {
return VetoGroup.MIXED;
}
}
return group;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Veto)) {
return false;
}
Veto other = (Veto) o;
return Objects.equals(vetoType, other.vetoType)
&& Objects.equals(reason, other.reason)
&& Objects.equals(score, other.score);
}
@Override
public int hashCode() {
return Objects.hash(vetoType, reason, score);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("vetoType", vetoType)
.add("reason", reason)
.add("score", score)
.toString();
}
}
/**
* An available resource in the cluster.
*/
class UnusedResource {
private final ResourceBag offer;
private final IHostAttributes attributes;
private final Optional<Instant> unavailabilityStart;
@VisibleForTesting
public UnusedResource(ResourceBag offer, IHostAttributes attributes) {
this(offer, attributes, Optional.absent());
}
public UnusedResource(ResourceBag offer, IHostAttributes attributes, Optional<Instant> start) {
this.offer = offer;
this.attributes = attributes;
this.unavailabilityStart = start;
}
public ResourceBag getResourceBag() {
return offer;
}
public IHostAttributes getAttributes() {
return attributes;
}
public Optional<Instant> getUnavailabilityStart() {
return unavailabilityStart;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof UnusedResource)) {
return false;
}
UnusedResource other = (UnusedResource) o;
return Objects.equals(offer, other.offer)
&& Objects.equals(attributes, other.attributes)
&& Objects.equals(unavailabilityStart, other.unavailabilityStart);
}
@Override
public int hashCode() {
return Objects.hash(offer, attributes, unavailabilityStart);
}
}
/**
* A request for resources in the cluster.
*/
class ResourceRequest {
private final ITaskConfig task;
private final ResourceBag request;
private final AttributeAggregate jobState;
public ResourceRequest(ITaskConfig task, ResourceBag request, AttributeAggregate jobState) {
this.task = task;
this.request = request;
this.jobState = jobState;
}
public Iterable<IConstraint> getConstraints() {
return task.getConstraints();
}
public ITaskConfig getTask() {
return task;
}
public ResourceBag getResourceBag() {
return request;
}
public AttributeAggregate getJobState() {
return jobState;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ResourceRequest)) {
return false;
}
ResourceRequest other = (ResourceRequest) o;
return Objects.equals(task, other.task)
&& Objects.equals(request, other.request)
&& Objects.equals(jobState, other.jobState);
}
@Override
public int hashCode() {
return Objects.hash(task, request, jobState);
}
}
/**
* Applies a task against the filter with the given resources, and on the host.
*
* @param resource An available resource in the cluster.
* @param request A resource request to match against the {@code resource}.
* @return A set of vetoes indicating reasons the task cannot be scheduled. If the task may be
* scheduled, the set will be empty.
*/
Set<Veto> filter(UnusedResource resource, ResourceRequest request);
}