All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.fenzo.AssignableVirtualMachine Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * 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.netflix.fenzo.functions.Action1;
import com.netflix.fenzo.plugins.ExclusiveHostConstraint;
import org.apache.mesos.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class represents a VM that contains resources that can be assigned to tasks.
 */
class AssignableVirtualMachine implements Comparable{

    /* package */ static final String PseuoHostNamePrefix = "FenzoPsueodHost-";

    private static class PortRange {
        private final VirtualMachineLease.Range range;
        private PortRange(VirtualMachineLease.Range range) {
            this.range = range;
        }
        int size() {
            return range.getEnd()-range.getBeg()+1;
        }
    }

    private static class PortRanges {
        private List ranges = new ArrayList<>();
        private List portRanges = new ArrayList<>();
        private int totalPorts=0;
        private int currUsedPorts=0;

        void addRanges(List ranges) {
            if(ranges!=null) {
                this.ranges.addAll(ranges);
                for(VirtualMachineLease.Range range: ranges) {
                    PortRange pRange = new PortRange(range);
                    portRanges.add(pRange);
                    totalPorts += pRange.size();
                }
            }
        }
        void clear() {
            ranges.clear();
            portRanges.clear();
            currUsedPorts=0;
            totalPorts=0;
        }
        private List getRanges() {
            return ranges;
        }
        boolean hasPorts(int num) {
            return num + currUsedPorts <= totalPorts;
        }
        private int consumeNextPort() {
            int forward=0;
            for(PortRange range: portRanges) {
                if(forward+range.size()>currUsedPorts) {
                    // consume in this range
                    return range.range.getBeg() + (currUsedPorts++ - forward);
                }
                else {
                    forward += range.size();
                }
            }
            throw new IllegalStateException("All ports (" + totalPorts + ") already used up");
        }
    }

    private static class ResAsgmntResult {
        private final List failures;
        private final double fitness;

        public ResAsgmntResult(List failures, double fitness) {
            this.failures = failures;
            this.fitness = fitness;
        }
    }

    private final Map leasesMap;
    private final BlockingQueue workersToUnAssign;
    private final BlockingQueue leasesToExpire;
    private final AtomicBoolean expireAllLeasesNow;
    private final Action1 leaseRejectAction;
    private final long leaseOfferExpirySecs;
    private final String hostname;
    private final Map currTotalScalars = new HashMap<>();
    private final Map currUsedScalars = new HashMap<>();
    private double currTotalCpus=0.0;
    private double currUsedCpus=0.0;
    private double currTotalMemory=0.0;
    private double currUsedMemory=0.0;
    private double currTotalNetworkMbps=0.0;
    private double currUsedNetworkMbps=0.0;
    private double currTotalDisk=0.0;
    private double currUsedDisk=0.0;
    private VirtualMachineLease currTotalLease=null;
    private PortRanges currPortRanges = new PortRanges();
    private volatile Map currAttributesMap = Collections.emptyMap();
    private final Map resourceSets = new HashMap<>();
    // previouslyAssignedTasksMap contains tasks on this VM before current scheduling iteration started. This is
    // available for optimization of scheduling assignments for such things as locality with other similar tasks, etc.
    private final Map previouslyAssignedTasksMap;
    // assignmentResults contains results of assignments on this VM from the current scheduling iteration; they
    // haven't been launched yet
    private final Map assignmentResults;
    private static final Logger logger = LoggerFactory.getLogger(AssignableVirtualMachine.class);
    private final ConcurrentMap leaseIdToHostnameMap;
    private final ConcurrentMap vmIdToHostnameMap;
    private String currVMId =null;
    private final TaskTracker taskTracker;
    private volatile long disabledUntil=0L;
    // This may have to be configurable, but, for now weight the job's soft constraints more than system wide fitness calculators
    private static double softConstraintFitnessWeightPercentage =50.0;
    private static double rSetsFitnessWeightPercentage=15.0;
    private String exclusiveTaskId =null;
    private final boolean singleLeaseMode;
    private boolean firstLeaseAdded=false;
    private final List consumedResourcesToAssign = new ArrayList<>();

    public AssignableVirtualMachine(ConcurrentMap vmIdToHostnameMap,
                                    ConcurrentMap leaseIdToHostnameMap,
                                    String hostname, Action1 leaseRejectAction,
                                    long leaseOfferExpirySecs, TaskTracker taskTracker) {
        this(vmIdToHostnameMap, leaseIdToHostnameMap, hostname, leaseRejectAction, leaseOfferExpirySecs, taskTracker, false);
    }

    public AssignableVirtualMachine(ConcurrentMap vmIdToHostnameMap,
                                    ConcurrentMap leaseIdToHostnameMap,
                                    String hostname, Action1 leaseRejectAction,
                                    long leaseOfferExpirySecs, TaskTracker taskTracker, boolean singleLeaseMode) {
        this.vmIdToHostnameMap = vmIdToHostnameMap;
        this.leaseIdToHostnameMap = leaseIdToHostnameMap;
        this.hostname = hostname;
        this.leaseRejectAction = getWrappedLeaseRejectAction(leaseRejectAction);
        this.leaseOfferExpirySecs = leaseOfferExpirySecs;
        this.taskTracker = taskTracker;
        this.leasesMap = new HashMap<>();
        this.leasesToExpire = new LinkedBlockingQueue<>();
        expireAllLeasesNow = new AtomicBoolean(false);
        this.workersToUnAssign = new LinkedBlockingQueue<>();
        this.previouslyAssignedTasksMap = new HashMap<>();
        this.assignmentResults = new HashMap<>();
        this.singleLeaseMode = singleLeaseMode;
    }

    private Action1 getWrappedLeaseRejectAction(final Action1 leaseRejectAction) {
        return leaseRejectAction==null?
                lease -> logger.warn("No lease reject action registered to reject lease id " + lease.getId() +
                        " on host " + lease.hostname()) :
                lease -> {
                    if (isRejectable(lease))
                        leaseRejectAction.call(lease);
                };
    }

    private boolean isRejectable(VirtualMachineLease l) {
        return l != null && l.getOffer() != null;
    }

    private void addToAvailableResources(VirtualMachineLease l) {
        if(singleLeaseMode && firstLeaseAdded)
            return; // ToDo should this be illegal state exception?
        firstLeaseAdded = true;
        final Map scalars = l.getScalarValues();
        if(scalars != null && !scalars.isEmpty()) {
            for(Map.Entry entry: scalars.entrySet()) {
                Double currVal = currTotalScalars.get(entry.getKey());
                if(currVal == null)
                    currVal = 0.0;
                currTotalScalars.put(entry.getKey(), currVal + entry.getValue());
            }
        }
        currTotalCpus += l.cpuCores();
        currTotalMemory += l.memoryMB();
        currTotalNetworkMbps += l.networkMbps();
        currTotalDisk += l.diskMB();
        if (l.portRanges() != null)
            currPortRanges.addRanges(l.portRanges());
        if (l.getAttributeMap() != null) {
            // always replace attributes map with the latest
            currAttributesMap = Collections.unmodifiableMap(new HashMap<>(l.getAttributeMap()));
        }
        for(Map.Entry entry: currAttributesMap.entrySet()) {
            switch (entry.getKey()) {
                case "res":
                    String val = entry.getValue().getText().getValue();
                    if(val!=null) {
                        StringTokenizer tokenizer = new StringTokenizer(val, "-");
                        String resName = tokenizer.nextToken();
                        switch (resName) {
                            case PreferentialNamedConsumableResourceSet.attributeName:
                                if(tokenizer.countTokens() == 3) {
                                    String name = tokenizer.nextToken();
                                    String val0Str = tokenizer.nextToken();
                                    String val1Str = tokenizer.nextToken();
                                    if(!resourceSets.containsKey(name)) {
                                        try {
                                            int val0 = Integer.parseInt(val0Str);
                                            int val1 = Integer.parseInt(val1Str);
                                            final PreferentialNamedConsumableResourceSet crs =
                                                    new PreferentialNamedConsumableResourceSet(name, val0, val1);
                                            final Iterator iterator = consumedResourcesToAssign.iterator();
                                            while(iterator.hasNext()) {
                                                TaskRequest request = iterator.next();
                                                crs.assign(request);
                                                iterator.remove();
                                            }
                                            resourceSets.put(name, crs);
                                        }
                                        catch (NumberFormatException e) {
                                            logger.warn(hostname + ": invalid resource spec (" + val + ") in attributes, ignoring: " + e.getMessage());
                                        }
                                    }
                                }
                                else
                                    logger.warn("Invalid res spec (expected 4 tokens with delimiter '-', ignoring: " + val);
                                break;
                            default:
                                logger.warn("Unknown resource in attributes, ignoring: " + val);
                        }
                    }
                    break;
            }
        }
        if(!consumedResourcesToAssign.isEmpty()) {
            throw new IllegalStateException(hostname + ": Some assigned tasks have no resource sets in offers: " +
                    consumedResourcesToAssign);
        }
    }

    void updateCurrTotalLease() {
        currTotalLease = createTotaledLease();
    }

    void resetResources() {
        if(!singleLeaseMode) {
            currTotalCpus=0.0;
            currTotalMemory=0.0;
            currTotalNetworkMbps=0.0;
            currTotalDisk=0.0;
            currPortRanges.clear();
            currTotalScalars.clear();
        }
        currUsedCpus=0.0;
        currUsedMemory=0.0;
        currUsedNetworkMbps=0.0;
        currUsedDisk=0.0;
        currUsedScalars.clear();
        // ToDo: in single offer mode, need to resolve used ports somehow
        // don't clear attribute map
        for(VirtualMachineLease l: leasesMap.values())
            addToAvailableResources(l);
    }

    VirtualMachineLease getCurrTotalLease() {
        return currTotalLease;
    }

    private VirtualMachineLease createTotaledLease() {
        return new VirtualMachineLease() {
            @Override
            public String getId() {
                return "InternalVMLeaseObject";
            }
            @Override
            public long getOfferedTime() {
                return System.currentTimeMillis();
            }
            @Override
            public String hostname() {
                return hostname;
            }
            @Override
            public String getVMID() {
                return "NoVMID-InternalVMLease";
            }
            @Override
            public double cpuCores() {
                return currTotalCpus;
            }
            @Override
            public double memoryMB() {
                return currTotalMemory;
            }
            @Override
            public double networkMbps() {
                return currTotalNetworkMbps;
            }
            @Override
            public double diskMB() {
                return currTotalDisk;
            }
            @Override
            public List portRanges() {
                return Collections.unmodifiableList(currPortRanges.getRanges());
            }
            @Override
            public Protos.Offer getOffer() {
                return null;
            }
            @Override
            public Map getAttributeMap() {
                return currAttributesMap;
            }
            @Override
            public Double getScalarValue(String name) {
                return currTotalScalars.get(name);
            }
            @Override
            public Map getScalarValues() {
                return Collections.unmodifiableMap(currTotalScalars);
            }
        };
    }

    void removeExpiredLeases(boolean all) {
        removeExpiredLeases(all, true);
    }

    void removeExpiredLeases(boolean all, boolean doRejectCallback) {
        @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") Set leasesToExpireIds = new HashSet<>();
        leasesToExpire.drainTo(leasesToExpireIds);
        Iterator> iterator = leasesMap.entrySet().iterator();
        boolean expireAll = expireAllLeasesNow.getAndSet(false) || all;
        while(iterator.hasNext()) {
            VirtualMachineLease l = iterator.next().getValue();
            if(expireAll || leasesToExpireIds.contains(l.getId())) {
                leaseIdToHostnameMap.remove(l.getId());
                if(expireAll) {
                    if(logger.isDebugEnabled())
                        logger.debug(hostname + ": expiring lease offer id " + l.getId());
                    if (doRejectCallback)
                        leaseRejectAction.call(l);
                }
                iterator.remove();
                if (logger.isDebugEnabled())
                    logger.debug("Removed lease on {}, all={}", hostname, all);
            }
        }
        if(expireAll && !hasPreviouslyAssignedTasks())
            resourceSets.clear();
    }

    int expireLimitedLeases(AssignableVMs.VMRejectLimiter vmRejectLimiter) {
        if(singleLeaseMode)
            return 0;
        long now = System.currentTimeMillis();
        for (VirtualMachineLease l: leasesMap.values()) {
            if (isRejectable(l) && l.getOfferedTime() < (now - leaseOfferExpirySecs * 1000) && vmRejectLimiter.reject()) {
                for (VirtualMachineLease vml: leasesMap.values()) {
                    leaseIdToHostnameMap.remove(vml.getId());
                    if(logger.isDebugEnabled())
                        logger.debug(getHostname() + ": expiring lease offer id " + l.getId());
                    leaseRejectAction.call(vml);
                }
                int size = leasesMap.values().size();
                leasesMap.clear();
                if (logger.isDebugEnabled())
                    logger.debug(hostname + ": cleared leases");
                return size;
            }
        }
        return 0;
    }

    String getCurrVMId() {
        return currVMId;
    }

    boolean addLease(VirtualMachineLease lease) {
        if (logger.isDebugEnabled())
            logger.debug("{}: adding lease id {}", hostname, lease.getId());
        if(singleLeaseMode && firstLeaseAdded)
            return false;
        if(!Objects.equals(currVMId, lease.getVMID())) {
            currVMId = lease.getVMID();
            vmIdToHostnameMap.put(lease.getVMID(), hostname);
        }
        if(System.currentTimeMillis()> entriesIterator = leasesMap.entrySet().iterator();
        while(entriesIterator.hasNext()) {
            Map.Entry entry = entriesIterator.next();
            leaseIdToHostnameMap.remove(entry.getValue().getId());
            leaseRejectAction.call(entry.getValue());
            entriesIterator.remove();
            if (logger.isDebugEnabled())
                logger.debug("Removed lease on " + hostname + " due to being disabled");
        }
    }

    public void enable() {
        disabledUntil = 0;
    }

    long getDisabledUntil() {
        return disabledUntil;
    }

    boolean isActive() {
        return !leasesMap.isEmpty() ||
                hasPreviouslyAssignedTasks() ||
                !assignmentResults.isEmpty() ||
                !leasesToExpire.isEmpty() ||
                !workersToUnAssign.isEmpty() ||
                System.currentTimeMillis() < disabledUntil;
    }

    boolean isAssignableNow() {
        return !isDisabled() && !leasesMap.isEmpty();
    }

    boolean isDisabled() {
        return System.currentTimeMillis() < disabledUntil;
    }

    void setAssignedTask(TaskRequest request) {
        if(logger.isDebugEnabled())
            logger.debug("{}: setting assigned task {}", hostname, request.getId());
        boolean added = taskTracker.addRunningTask(request, this);
        if(added) {
            assignResourceSets(request);
        }
        else
            logger.error("Unexpected to add duplicate task id=" + request.getId());
        previouslyAssignedTasksMap.put(request.getId(), request);
        setIfExclusive(request);
        if(singleLeaseMode && added) {
            removeResourcesOf(request);
        }
    }

    private void assignResourceSets(TaskRequest request) {
        if(request.getAssignedResources() != null) {
            final List consumedNamedResources =
                    request.getAssignedResources().getConsumedNamedResources();
            if(consumedNamedResources != null && !consumedNamedResources.isEmpty()) {
                for(PreferentialNamedConsumableResourceSet.ConsumeResult cr: consumedNamedResources) {
                    if(resourceSets.get(cr.getAttrName()) == null)
                        consumedResourcesToAssign.add(request); // resource set not available yet
                    else
                        resourceSets.get(cr.getAttrName()).assign(request);
                }
            }
        }
    }

    void expireLease(String leaseId) {
        logger.info("Got request to expire lease on " + hostname);
        leasesToExpire.offer(leaseId);
    }

    void expireAllLeases() {
        expireAllLeasesNow.set(true);
    }

    void markTaskForUnassigning(String taskId) {
        workersToUnAssign.offer(taskId);
    }

    private void setIfExclusive(TaskRequest request) {
        if(request.getHardConstraints()!=null) {
            for(ConstraintEvaluator evaluator: request.getHardConstraints()) {
                if(evaluator instanceof ExclusiveHostConstraint) {
                    exclusiveTaskId = request.getId();
                    return;
                }
            }
        }
    }

    private void clearIfExclusive(String taskId) {
        if(taskId.equals(exclusiveTaskId))
            exclusiveTaskId = null;
    }

    void prepareForScheduling() {
        @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") List tasks = new ArrayList<>();
        workersToUnAssign.drainTo(tasks);
        for(String t: tasks) {
            if(logger.isDebugEnabled())
                logger.debug("{}: removing previously assigned task {}", hostname, t);
            taskTracker.removeRunningTask(t);
            TaskRequest r = previouslyAssignedTasksMap.remove(t);
            if(singleLeaseMode && r!=null)
                addBackResourcesOf(r);
            releaseResourceSets(r);
            clearIfExclusive(t);
        }
        assignmentResults.clear();
    }

    private void releaseResourceSets(TaskRequest r) {
        if(r==null) {
            logger.warn("Can't release resource sets for null task");
            return;
        }
        // unassign resource sets if any
        final Map customNamedResources = r.getCustomNamedResources();
        for (Map.Entry entry : resourceSets.entrySet()) {
            entry.getValue().release(r);
        }
    }

    private void removeResourcesOf(TaskRequest request) {
        currTotalCpus -= request.getCPUs();
        currTotalMemory -= request.getMemory();
        currTotalDisk -= request.getDisk();
        currTotalNetworkMbps -= request.getNetworkMbps();
        final Map scalarRequests = request.getScalarRequests();
        if(scalarRequests != null && !scalarRequests.isEmpty()) {
            for(Map.Entry entry: scalarRequests.entrySet()) {
                Double oldVal = currTotalScalars.get(entry.getKey());
                if(oldVal != null) {
                    double newVal = oldVal - entry.getValue();
                    if(newVal < 0.0) {
                        logger.warn(hostname + ": Scalar resource " + entry.getKey() + " is " + newVal + " after removing " +
                                entry.getValue() + " from task " + request.getId());
                        currTotalScalars.put(entry.getKey(), 0.0);
                    }
                    else
                        currTotalScalars.put(entry.getKey(), newVal);
                }
            }
        }
        // ToDo need to figure out ports as well
    }

    private void addBackResourcesOf(TaskRequest r) {
        currTotalCpus += r.getCPUs();
        currTotalMemory += r.getMemory();
        currTotalNetworkMbps += r.getNetworkMbps();
        currTotalDisk += r.getDisk();
        final Map scalarRequests = r.getScalarRequests();
        if(scalarRequests != null && !scalarRequests.isEmpty()) {
            for(Map.Entry entry: scalarRequests.entrySet()) {
                Double oldVal = currTotalScalars.get(entry.getKey());
                if(oldVal == null)
                    oldVal = 0.0;
                currTotalScalars.put(entry.getKey(), oldVal + entry.getValue());
            }
        }
        // ToDo queueTask back ports
    }

    String getAttrValue(String attrName) {
        if(getCurrTotalLease()==null)
            return null;
        Protos.Attribute attribute = getCurrTotalLease().getAttributeMap().get(attrName);
        if(attribute==null)
            return null;
        return attribute.getText().getValue();
    }

    Map getMaxScalars() {
        Map result = new HashMap<>();
        if (currTotalScalars != null) {
            for (Map.Entry e : currTotalScalars.entrySet()) {
                result.put(e.getKey(), e.getValue());
            }
        }
        if (hasPreviouslyAssignedTasks()) {
            for (TaskRequest t: previouslyAssignedTasksMap.values()) {
                final Map scalarRequests = t.getScalarRequests();
                if (scalarRequests != null && !scalarRequests.isEmpty()) {
                    for (Map.Entry e: scalarRequests.entrySet()) {
                        if (result.get(e.getKey()) == null)
                            result.put(e.getKey(), e.getValue());
                        else
                            result.put(e.getKey(), e.getValue() + result.get(e.getKey()));
                    }
                }
            }
        }
        return result;
    }

    Map getMaxResources() {
        double cpus=0.0;
        double memory=0.0;
        double network=0.0;
        double ports=0.0;
        double disk=0.0;
        for(TaskRequest r: previouslyAssignedTasksMap.values()) {
            cpus += r.getCPUs();
            memory += r.getMemory();
            network += r.getNetworkMbps();
            ports += r.getPorts();
            disk += r.getDisk();
        }
        cpus += getCurrTotalLease().cpuCores();
        memory += getCurrTotalLease().memoryMB();
        network += getCurrTotalLease().networkMbps();
        List ranges = getCurrTotalLease().portRanges();
        for(VirtualMachineLease.Range r: ranges)
            ports += r.getEnd()-r.getBeg();
        disk += getCurrTotalLease().diskMB();
        Map result = new HashMap<>();
        result.put(VMResource.CPU, cpus);
        result.put(VMResource.Memory, memory);
        result.put(VMResource.Network, network);
        result.put(VMResource.Ports, ports);
        result.put(VMResource.Disk, disk);
        return result;
    }

    /**
     * Try assigning resources for a given task.
     * This is the main allocation method to allocate resources from this VM to a given task. This method evaluates
     * hard constraints first. Then, it tries to assign resources. If either of these results in failures, it returns a
     * failure result. If successful, it invokes the fitness calculator to determine the fitness value. Then, it
     * evaluates soft constraints to get its fitness value. The resulting fitness value is reduced as a
     * weighted average of the two fitness values.
     *
     * @param request The task request to assign resources to.
     * @param fitnessCalculator The fitness calculator to use for resource assignment.
     * @return Assignment result.
     */
    TaskAssignmentResult tryRequest(TaskRequest request, VMTaskFitnessCalculator fitnessCalculator) {
        if(logger.isDebugEnabled())
            logger.debug("Host {} task {}: #leases: {}", getHostname(), request.getId(), leasesMap.size());
        if(leasesMap.isEmpty())
            return null;
        if(exclusiveTaskId!=null) {
            if(logger.isDebugEnabled())
                logger.debug("Host {}: can't assign task {}, already have task {} assigned with exclusive host constraint",
                        hostname, request.getId(), exclusiveTaskId);
            ConstraintFailure failure = new ConstraintFailure(ExclusiveHostConstraint.class.getName(),
                    "Already has task " + exclusiveTaskId + " with exclusive host constraint");
            return new TaskAssignmentResult(this, request, false, null, failure, 0.0);
        }
        VirtualMachineCurrentState vmCurrentState = vmCurrentState();
        TaskTrackerState taskTrackerState = taskTrackerState();
        ConstraintFailure failedHardConstraint = findFailedHardConstraints(request, vmCurrentState, taskTrackerState);
        if(failedHardConstraint!=null) {
            if(logger.isDebugEnabled())
                logger.debug("Host {}: task {} failed hard constraint: ", hostname, request.getId(), failedHardConstraint);
            return new TaskAssignmentResult(this, request, false, null, failedHardConstraint, 0.0);
        }
        final ResAsgmntResult resAsgmntResult = evalAndGetResourceAssignmentFailures(request);
        if(!resAsgmntResult.failures.isEmpty()) {
            if(logger.isDebugEnabled()) {
                StringBuilder b = new StringBuilder();
                for(AssignmentFailure f: resAsgmntResult.failures)
                    b.append(f.toString()).append(" ; ");
                logger.debug("{}: task {} failed assignment: {}", hostname, request.getId(), b.toString());
            }
            return new TaskAssignmentResult(this, request, false, resAsgmntResult.failures, null, 0.0);
        }
        final double resAsgmntFitness = resAsgmntResult.fitness;
        double fitness = fitnessCalculator.calculateFitness(request, vmCurrentState, taskTrackerState);
        if(fitness == 0.0) {
            if(logger.isDebugEnabled())
                logger.debug("{}: task {} fitness calculator returned 0.0", hostname, request.getId());
            List failures = Collections.singletonList(
                    new AssignmentFailure(VMResource.Fitness, 0.0, 0.0, 0.0, "fitnessCalculator: 0.0"));
            return new TaskAssignmentResult(this, request, false, failures, null, fitness);
        }
        List softConstraints = request.getSoftConstraints();
        // we don't fail on soft constraints
        double softConstraintFitness=1.0;
        if(softConstraints!=null && !softConstraints.isEmpty()) {
            softConstraintFitness = getSoftConstraintsFitness(request, vmCurrentState, taskTrackerState);
        }
        fitness = combineFitnessValues(resAsgmntFitness, fitness, softConstraintFitness);
        return new TaskAssignmentResult(this, request, true, null, null, fitness);
    }

    private double combineFitnessValues(double resAsgmntFitness, double fitness, double softConstraintFitness) {
        return ( resAsgmntFitness * rSetsFitnessWeightPercentage +
                softConstraintFitness * softConstraintFitnessWeightPercentage +
                fitness * (100.0 - rSetsFitnessWeightPercentage - softConstraintFitnessWeightPercentage) )
                / 100.0;
    }

    private double getSoftConstraintsFitness(TaskRequest request, VirtualMachineCurrentState vmCurrentState, TaskTrackerState taskTrackerState) {
        List softConstraints = request.getSoftConstraints();
        int n=0;
        double sum=0.0;
        for(VMTaskFitnessCalculator s: softConstraints) {
            n++;
            sum += s.calculateFitness(request, vmCurrentState, taskTrackerState);
        }
        return sum/n;
    }

    private ResAsgmntResult evalAndGetResourceAssignmentFailures(TaskRequest request) {
        List failures = new ArrayList<>();
        final Map scalarRequests = request.getScalarRequests();
        if(scalarRequests != null && !scalarRequests.isEmpty()) {
            for(Map.Entry entry: scalarRequests.entrySet()) {
                if(entry.getValue() == null)
                    continue;
                Double u = currUsedScalars.get(entry.getKey());
                if(u == null)  u = 0.0;
                Double t = currTotalScalars.get(entry.getKey());
                if(t == null)  t=0.0;
                if(u + entry.getValue() > t) {
                    failures.add(new AssignmentFailure(
                            VMResource.Other, entry.getValue(), u, t, entry.getKey()
                    ));
                }
            }
        }
        if((currUsedCpus+request.getCPUs()) > currTotalCpus) {
            AssignmentFailure failure = new AssignmentFailure(
                    VMResource.CPU, request.getCPUs(), currUsedCpus,
                    currTotalCpus, "");
            //logger.info(hostname+":"+request.getId()+" Insufficient cpus: " + failure.toString());
            failures.add(failure);
        }
        if((currUsedMemory+request.getMemory()) > currTotalMemory) {
            AssignmentFailure failure = new AssignmentFailure(
                    VMResource.Memory, request.getMemory(), currUsedMemory,
                    currTotalMemory, "");
            //logger.info(hostname+":"+request.getId()+" Insufficient memory: " + failure.toString());
            failures.add(failure);
        }
        if((currUsedNetworkMbps+request.getNetworkMbps()) > currTotalNetworkMbps) {
            AssignmentFailure failure = new AssignmentFailure(
                    VMResource.Network, request.getNetworkMbps(), currUsedNetworkMbps, currTotalNetworkMbps, "");
            //logger.info(hostname+":"+request.getId()+" Insufficient network: " + failure.toString());
            failures.add(failure);
        }
        if((currUsedDisk+request.getDisk()) > currTotalDisk) {
            AssignmentFailure failure =
                    new AssignmentFailure(VMResource.Disk, request.getDisk(), currUsedDisk, currTotalDisk, "");
            //logger.info(hostname+":"+request.getId()+" Insufficient disk: " + failure.toString());
            failures.add(failure);
        }
        if(!currPortRanges.hasPorts(request.getPorts())) {
            AssignmentFailure failure = new AssignmentFailure(
                    VMResource.Ports, request.getPorts(), currPortRanges.currUsedPorts,
                    currPortRanges.totalPorts, "");
            //logger.info(hostname+":"+request.getId()+" Insufficient ports: " + failure.toString());
            failures.add(failure);
        }
        double rSetFitness=0.0;
        int numRSets=0;
        final Set requestedNamedResNames = new HashSet<>(request.getCustomNamedResources()==null? Collections.emptySet() :
                request.getCustomNamedResources().keySet());
        if(failures.isEmpty()) {
            // perform resource set checks only if no other assignment failures so far
            for (Map.Entry entry : resourceSets.entrySet()) {
                if (!requestedNamedResNames.isEmpty())
                    requestedNamedResNames.remove(entry.getKey());
                final double fitness = entry.getValue().getFitness(request);
                if (fitness == 0.0) {
                    AssignmentFailure failure = new AssignmentFailure(VMResource.ResourceSet, 0.0, 0.0, 0.0,
                            "ResourceSet " + entry.getValue().getName() + " unavailable"
                    );
                    failures.add(failure);
                } else {
                    rSetFitness += fitness;
                    numRSets++;
                }
            }
            if (!requestedNamedResNames.isEmpty()) {
                // task requested resourceSets that aren't available on this host
                AssignmentFailure failure = new AssignmentFailure(VMResource.ResourceSet, 0.0, 0.0, 0.0,
                        "UnavailableResourceSets: " + requestedNamedResNames
                );
                failures.add(failure);
            } else {
                if (!failures.isEmpty()) {
                    rSetFitness = 0.0;
                } else if (numRSets > 1)
                    rSetFitness /= numRSets;
            }
        }
        return new ResAsgmntResult(failures, rSetFitness);
    }

    private TaskTrackerState taskTrackerState() {
        return new TaskTrackerState() {
            @Override
            public Map getAllRunningTasks() {
                return taskTracker.getAllRunningTasks();
            }

            @Override
            public Map getAllCurrentlyAssignedTasks() {
                return taskTracker.getAllAssignedTasks();
            }
        };
    }

    VirtualMachineCurrentState getVmCurrentState() {
        final List offers = new LinkedList<>();
        for (VirtualMachineLease l: leasesMap.values()) {
            offers.add(l.getOffer());
        }
        return new VirtualMachineCurrentState() {
            @Override
            public String getHostname() {
                return hostname;
            }
            @Override
            public Map getResourceSets() {
                return resourceSets;
            }
            @Override
            public VirtualMachineLease getCurrAvailableResources() {
                return currTotalLease;
            }

            @Override
            public Collection getAllCurrentOffers() {
                System.out.println("****************************** ");
                return offers;
            }

            @Override
            public Collection getTasksCurrentlyAssigned() {
                return Collections.emptyList();
            }
            @Override
            public Collection getRunningTasks() {
                return Collections.unmodifiableCollection(previouslyAssignedTasksMap.values());
            }
            @Override
            public long getDisabledUntil() {
                return disabledUntil;
            }
        };
    }

    private VirtualMachineCurrentState vmCurrentState() {
        final List offers = new LinkedList<>();
        for (VirtualMachineLease l: leasesMap.values()) {
            offers.add(l.getOffer());
        }
        return new VirtualMachineCurrentState() {
            @Override
            public String getHostname() {
                return hostname;
            }
            @Override
            public Map getResourceSets() {
                return resourceSets;
            }
            @Override
            public VirtualMachineLease getCurrAvailableResources() {
                return currTotalLease;
            }

            @Override
            public Collection getAllCurrentOffers() {
                return offers;
            }

            @Override
            public Collection getTasksCurrentlyAssigned() {
                return Collections.unmodifiableCollection(assignmentResults.values());
            }
            @Override
            public Collection getRunningTasks() {
                return Collections.unmodifiableCollection(previouslyAssignedTasksMap.values());
            }
            @Override
            public long getDisabledUntil() {
                return disabledUntil;
            }
        };
    }

    private ConstraintFailure findFailedHardConstraints(TaskRequest request, VirtualMachineCurrentState vmCurrentState, TaskTrackerState taskTrackerState) {
        List hardConstraints = request.getHardConstraints();
        if(hardConstraints==null || hardConstraints.isEmpty())
            return null;
        for(ConstraintEvaluator c: hardConstraints) {
            ConstraintEvaluator.Result r = c.evaluate(request, vmCurrentState, taskTrackerState);
            if(!r.isSuccessful())
                return new ConstraintFailure(c.getName(), r.getFailureReason());
        }
        return null;
    }

    String getHostname() {
        return hostname;
    }

    boolean hasPreviouslyAssignedTasks() {
        return !previouslyAssignedTasksMap.isEmpty();
    }

    /**
     * Assign the given result and update internal counters for used resources. Use this to assign an individual
     * assignment result within a scheduling iteration.
     *
     * @param result The assignment result to assign.
     */
    void assignResult(TaskAssignmentResult result) {
        final Map scalarRequests = result.getRequest().getScalarRequests();
        if(scalarRequests != null && !scalarRequests.isEmpty()) {
            for(Map.Entry entry: scalarRequests.entrySet()) {
                if(entry.getValue() == null)
                    continue;
                Double u = currUsedScalars.get(entry.getKey());
                if(u == null)  u = 0.0;
                currUsedScalars.put(entry.getKey(), u + entry.getValue());
            }
        }
        currUsedCpus += result.getRequest().getCPUs();
        currUsedMemory += result.getRequest().getMemory();
        currUsedNetworkMbps += result.getRequest().getNetworkMbps();
        currUsedDisk += result.getRequest().getDisk();
        for(int p=0; p entry: resourceSets.entrySet()) {
            result.addResourceSet(entry.getValue().consume(result.getRequest()));
        }
        if(!taskTracker.addAssignedTask(result.getRequest(), this))
            logger.error("Unexpected to re-add task to assigned state, id=" + result.getRequest().getId());
        assignmentResults.put(result.getRequest(), result);
    }

    /**
     * Reset the assignment results of current scheduling iteration and return the total assignment result for this VM.
     * Use this at the end of the scheduling iteration. Include all of the assignment results as well as all of the VM
     * leases available in the result.
     *
     * @return Total assignment result including the tasks assigned and VM leases used.
     */
    VMAssignmentResult resetAndGetSuccessfullyAssignedRequests() {
        if(assignmentResults.isEmpty())
            return null;
        Set result = new HashSet<>();
        for(Map.Entry entry: assignmentResults.entrySet())
            if(entry.getValue().isSuccessful())
                result.add(entry.getValue());
        if(result.isEmpty())
            return null;
        VMAssignmentResult vmar = new VMAssignmentResult(hostname, new ArrayList<>(leasesMap.values()), result);
        if(!singleLeaseMode) {
            for(String l: leasesMap.keySet())
                leaseIdToHostnameMap.remove(l);
            leasesMap.clear();
        }
        assignmentResults.clear();
        return vmar;
    }

    // Only makes sense to get called after leases have been consolidated and total resources set in
    // {@Code setAvailableResources()}
    @Override
    public int compareTo(AssignableVirtualMachine o) {
        if(o == null)
            return -1;
        if(o.leasesMap.isEmpty())
            return -1;
        if(leasesMap.isEmpty())
            return 1;
        return Double.compare(o.currTotalCpus, currTotalCpus);
    }

    /**
     * Get resource status, showing used and available amounts. The available amounts are in addition to the amounts used.
     *
     * @return Map with keys containing resources and values containing corresponding usage represented as a two number
     * array, where the first represents the used amounts and the second represents additional available amounts.
     */
    Map getResourceStatus() {
        Map resourceMap = new HashMap<>();
        double cpusUsed=0.0;
        double memUsed=0.0;
        double portsUsed=0.0;
        double networkUsed=0.0;
        double diskUsed=0.0;
        for(TaskRequest r: previouslyAssignedTasksMap.values()) {
            cpusUsed += r.getCPUs();
            memUsed += r.getMemory();
            portsUsed += r.getPorts();
            networkUsed += r.getNetworkMbps();
            diskUsed += r.getDisk();
        }
        double cpusAvail=0.0;
        double memAvail=0.0;
        double portsAvail=0;
        double networkAvail=0.0;
        double diskAvail=0.0;
        for(VirtualMachineLease l: leasesMap.values()) {
            cpusAvail += l.cpuCores();
            memAvail += l.memoryMB();
            for(VirtualMachineLease.Range range: l.portRanges())
                portsAvail += range.getEnd()-range.getBeg();
            networkAvail += l.networkMbps();
            diskAvail += l.diskMB();
        }
        resourceMap.put(VMResource.CPU, new Double[]{cpusUsed, cpusAvail});
        resourceMap.put(VMResource.Memory, new Double[]{memUsed, memAvail});
        resourceMap.put(VMResource.Ports, new Double[]{portsUsed, portsAvail});
        resourceMap.put(VMResource.Network, new Double[]{networkUsed, networkAvail});
        resourceMap.put(VMResource.Disk, new Double[]{diskUsed, diskAvail});
        // put resource sets
        for(PreferentialNamedConsumableResourceSet rSet: resourceSets.values()) {
            final String name = rSet.getName();
            final List usedCounts = rSet.getUsedCounts();
            int used=0;
            for(Double c: usedCounts) {
                if(c>=0)
                    used++;
            }
            resourceMap.put(VMResource.ResourceSet, new Double[]{(double)used, (double)(usedCounts.size()-used)});
        }
        return resourceMap;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy