com.netflix.fenzo.PreferentialNamedConsumableResourceSet Maven / Gradle / Ivy
/*
* Copyright 2015 Netflix, Inc.
*
* 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 com.netflix.fenzo;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This encapsulates preferential resource sets available on a VM. Resource sets are two level resources that can
* be assigned to tasks that specify a name to reserve for an available resource set, and the number of sub-resources
* (second level of the two level resource) it needs.
* A {@link PreferentialNamedConsumableResourceSet} contains 1 or more resource sets,
* {@link com.netflix.fenzo.PreferentialNamedConsumableResourceSet.PreferentialNamedConsumableResource}, each of which
* can be assigned (or reserved to) a name requested by tasks, if currently unassigned. Each resource set contains one
* or more count of resources available for assignment to tasks.
*
A task can be assigned to one of the resource sets if it either has no tasks assigned to it, or the name assigned
* to the resource set matches what the task being assigned is requesting. The number of sub-resources requested by task
* is also checked. Tasks may request 0 or more sub-resources. The assignment attempts to use as few resource sets as
* possible and returns a fitness score that helps scheduler pick between multiple VMs that can potentially fit the task.
* The resulting assignment contains the index of the resource set assigned. The resource sets are assigned indexes
* starting with 0.
*/
public class PreferentialNamedConsumableResourceSet {
final static String attributeName = "ResourceSet";
private static String getResNameVal(String name, TaskRequest request) {
final Map customNamedResources = request.getCustomNamedResources();
if(customNamedResources!=null) {
final TaskRequest.NamedResourceSetRequest setRequest = customNamedResources.get(name);
return setRequest==null? CustomResAbsentKey : setRequest.getResValue();
}
return CustomResAbsentKey;
}
public static class ConsumeResult {
private final int index;
private final String attrName;
private final String resName;
private final double fitness;
@JsonCreator
@JsonIgnoreProperties(ignoreUnknown=true)
public ConsumeResult(@JsonProperty("index") int index,
@JsonProperty("attrName") String attrName,
@JsonProperty("resName") String resName,
@JsonProperty("fitness") double fitness) {
this.index = index;
this.attrName = attrName;
this.resName = resName;
this.fitness = fitness;
}
public int getIndex() {
return index;
}
public String getAttrName() {
return attrName;
}
public String getResName() {
return resName;
}
public double getFitness() {
return fitness;
}
}
public static class PreferentialNamedConsumableResource {
private final double maxFitness;
private final int index;
private final String attrName;
private String resName=null;
private final int limit;
private final Map usageBy;
private int usedSubResources=0;
PreferentialNamedConsumableResource(int i, String attrName, int limit) {
this.index = i;
this.attrName = attrName;
this.limit = limit;
usageBy = new HashMap<>();
// we add 1.0 to max fitness possible since we add 1.0 for the situation when there is already a task
// assigned with the same resValue even though it uses 0.0 subResources, versus, there are no assignments yet.
maxFitness = limit + 1.0;
}
public int getIndex() {
return index;
}
public String getResName() {
return resName;
}
public int getLimit() {
return limit;
}
public Map getUsageBy() {
return usageBy;
}
double getUsedCount() {
if(resName==null)
return -1;
return usedSubResources;
}
double getFitness(TaskRequest request) {
String r = getResNameVal(attrName, request);
if(resName == null)
return 0.5 / maxFitness; // unassigned: 0.0 indicates no fitness, so return 0.5, which is less than
// the case of assigned with 0 sub-resources
if(!resName.equals(r))
return 0.0;
final TaskRequest.NamedResourceSetRequest setRequest = request.getCustomNamedResources()==null?
null : request.getCustomNamedResources().get(attrName);
double subResNeed = setRequest==null? 0.0 : setRequest.getNumSubResources();
if(usedSubResources + subResNeed > limit)
return 0.0;
return Math.min(1.0, usedSubResources + subResNeed + 1.0 / maxFitness);
}
void consume(TaskRequest request) {
String r = getResNameVal(attrName, request);
consume(r, request);
}
void consume(String assignedResName, TaskRequest request) {
if(usageBy.get(request.getId()) != null)
return; // already consumed
if(resName!=null && !resName.equals(assignedResName))
throw new IllegalStateException(this.getClass().getName() + " already consumed by " + resName +
", can't consume for " + assignedResName);
if(resName == null) {
resName = assignedResName;
usageBy.clear();
}
final TaskRequest.NamedResourceSetRequest setRequest = request.getCustomNamedResources()==null?
null : request.getCustomNamedResources().get(attrName);
double subResNeed = setRequest==null? 0.0 : setRequest.getNumSubResources();
if(usedSubResources + subResNeed > limit)
throw new RuntimeException(this.getClass().getName() + " already consumed for " + resName +
" up to the limit of " + limit);
usageBy.put(request.getId(), setRequest);
usedSubResources += subResNeed;
}
boolean release(TaskRequest request) {
String r = getResNameVal(attrName, request);
if(resName != null && !resName.equals(r)) {
return false;
}
final TaskRequest.NamedResourceSetRequest removed = usageBy.remove(request.getId());
if(removed == null)
return false;
usedSubResources -= removed.getNumSubResources();
if(usageBy.isEmpty())
resName = null;
return true;
}
}
public static final String CustomResAbsentKey = "CustomResAbsent";
private final String name;
private final List usageBy;
public PreferentialNamedConsumableResourceSet(String name, int val0, int val1) {
this.name = name;
usageBy = new ArrayList<>(val0);
for(int i=0; i consumedNamedResources = assignedResources.getConsumedNamedResources();
if(consumedNamedResources!=null && !consumedNamedResources.isEmpty()) {
for(PreferentialNamedConsumableResourceSet.ConsumeResult consumeResult: consumedNamedResources) {
if(name.equals(consumeResult.getAttrName())) {
final int index = consumeResult.getIndex();
if(index < 0 || index > usageBy.size())
throw new IllegalStateException("Illegal assignment of namedResource " + name +
": has " + usageBy.size() + " resource sets, can't assign to index " + index
);
usageBy.get(index).consume(consumeResult.getResName(), request);
}
}
}
}
}
// returns 0.0 for no fitness at all, or <=1.0 for fitness
double getFitness(TaskRequest request) {
return consumeIntl(request, true).fitness;
}
private ConsumeResult consumeIntl(TaskRequest request, boolean skipConsume) {
PreferentialNamedConsumableResource best = null;
double bestFitness=0.0;
for(PreferentialNamedConsumableResource r: usageBy) {
double f = r.getFitness(request);
if(f == 0.0)
continue;
if(bestFitness < f) {
best = r;
bestFitness = f;
}
}
if(!skipConsume) {
if (best == null)
throw new RuntimeException("Unexpected to have no availability for job " + request.getId() + " for consumable resource " + name);
best.consume(request);
}
return new ConsumeResult(
best==null? -1 : best.index,
best==null? null : best.attrName,
best==null? null : best.resName,
bestFitness
);
}
boolean release(TaskRequest request) {
for(PreferentialNamedConsumableResource r: usageBy)
if(r.release(request))
return true;
return false;
}
int getNumSubResources() {
return usageBy.get(0).getLimit()-1;
}
List getUsedCounts() {
List counts = new ArrayList<>(usageBy.size());
for(PreferentialNamedConsumableResource r: usageBy)
counts.add(r.getUsedCount());
return counts;
}
}