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

com.netflix.fenzo.AssignableVirtualMachine 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.netflix.fenzo.plugins.ExclusiveHostConstraint;
import org.apache.mesos.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.functions.Action1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

class AssignableVirtualMachine implements Comparable{

    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) {
            if(num+currUsedPorts > totalPorts)
                return false;
            return true;
        }
        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 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;
    // ToDo abstract out into VMResources
    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 Map currAttributesMap = 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 double softConstraintWeightPercentage=75.0;
    private String exclusiveTaskId =null;

    public AssignableVirtualMachine(ConcurrentMap vmIdToHostnameMap,
                                    ConcurrentMap leaseIdToHostnameMap,
                                    String hostname, Action1 leaseRejectAction,
                                    long leaseOfferExpirySecs, TaskTracker taskTracker) {
        this.vmIdToHostnameMap = vmIdToHostnameMap;
        this.leaseIdToHostnameMap = leaseIdToHostnameMap;
        this.hostname = hostname;
        this.leaseRejectAction = leaseRejectAction==null?
                new Action1() {
                    @Override
                    public void call(VirtualMachineLease lease) {
                        logger.warn("No lease reject action registered to reject lease id " + lease.getId() +
                                " on host " + lease.hostname());
                    }
                } :
                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<>();
    }

    private void setAvailableResources() {
        resetResources(!leasesMap.isEmpty()); // clear attributes only if there is at least one offer, else keep attributes from before
        for(VirtualMachineLease l: leasesMap.values()) {
            currTotalCpus += l.cpuCores();
            currTotalMemory += l.memoryMB();
            currTotalNetworkMbps += l.networkMbps();
            currTotalDisk += l.diskMB();
            if(l.portRanges()!=null)
                currPortRanges.addRanges(l.portRanges());
            if(l.getAttributeMap()!=null)
                currAttributesMap.putAll(l.getAttributeMap());
        }
        currTotalLease = createTotaledLease();
    }

    private void resetResources(boolean clearAttributes) {
        currTotalCpus=0.0;
        currUsedCpus=0.0;
        currTotalMemory=0.0;
        currUsedMemory=0.0;
        currTotalNetworkMbps=0.0;
        currUsedNetworkMbps=0.0;
        currTotalDisk=0.0;
        currUsedDisk=0.0;
        currPortRanges.clear();
        if(clearAttributes)
            currAttributesMap.clear();
    }

    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 Collections.unmodifiableMap(currAttributesMap);
            }
        };
    }

    int removeExpiredLeases(AssignableVMs.VMRejectLimiter vmRejectLimiter, boolean all) {
        int rejected=0;
        long now = System.currentTimeMillis();
        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)
                    leaseRejectAction.call(l);
                iterator.remove();
            }
            else if(l.getOfferedTime() < (now - leaseOfferExpirySecs*1000) && vmRejectLimiter.reject()) {
                leaseIdToHostnameMap.remove(l.getId());
                leaseRejectAction.call(l);
                iterator.remove();
                rejected++;
            }
        }
        return rejected;
    }

    String getCurrVMId() {
        return currVMId;
    }

    boolean addLease(VirtualMachineLease lease) {
        if(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();
        }
    }

    public void enable() {
        disabledUntil = 0;
    }

    long getDisabledUntil() {
        return disabledUntil;
    }

    /* package */ boolean isActive() {
        if( !leasesMap.isEmpty() ||
                hasPreviouslyAssignedTasks() ||
                !leasesToExpire.isEmpty() ||
                !workersToUnAssign.isEmpty() ||
                System.currentTimeMillis()disabledUntil) && !leasesMap.isEmpty();
    }

    void setAssignedTask(TaskRequest request) {
        if(!taskTracker.addRunningTask(request, this))
            logger.error("Unexpected to add duplicate task id=" + request.getId());
        previouslyAssignedTasksMap.put(request.getId(), request);
        setIfExclusive(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() {
        List tasks = new ArrayList<>();
        workersToUnAssign.drainTo(tasks);
        for(String t: tasks) {
            taskTracker.removeRunningTask(t);
            previouslyAssignedTasksMap.remove(t);
            clearIfExclusive(t);
        }
        assignmentResults.clear();
        setAvailableResources();
    }

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

    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;
    }

    TaskAssignmentResult tryRequest(TaskRequest request, VMTaskFitnessCalculator fitnessCalculator) {
        //logger.info("    #leases=" + leases.size());
        if(leasesMap.isEmpty())
            return null;
        if(exclusiveTaskId!=null) {
            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) {
            return new TaskAssignmentResult(this, request, false, null, failedHardConstraint, 0.0);
        }
        List failures = evalAndGetResourceAssignmentFailures(request);
        if(!failures.isEmpty()) {
            return new TaskAssignmentResult(this, request, false, failures, null, 0.0);
        }
        double fitness = fitnessCalculator.calculateFitness(request, vmCurrentState, taskTrackerState);
        if(fitness == 0.0) {
            failures.add(new AssignmentFailure(VMResource.Fitness, 1.0, 1.0, 0.0));
            return new TaskAssignmentResult(this, request, false, failures, null, fitness);
        }
        List softConstraints = request.getSoftConstraints();
        // we don't fail on soft constraints
        if(softConstraints!=null && !softConstraints.isEmpty()) {
            double softConstraintFitness = getSoftConstraintsFitness(request, vmCurrentState, taskTrackerState);
            fitness = ((softConstraintFitness*softConstraintWeightPercentage) + (fitness*(100.0-softConstraintWeightPercentage)))
                    / 100.0;
        }
        return new TaskAssignmentResult(this, request, true, null, null, fitness);
    }

    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 List evalAndGetResourceAssignmentFailures(TaskRequest request) {
        List failures = new ArrayList<>();
        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);
        }
        return failures;
    }

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

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

    VirtualMachineCurrentState getVmCurrentState() {
        return new VirtualMachineCurrentState() {
            @Override
            public String getHostname() {
                return hostname;
            }
            @Override
            public VirtualMachineLease getCurrAvailableResources() {
                return currTotalLease;
            }
            @Override
            public Collection getTasksCurrentlyAssigned() {
                return Collections.EMPTY_LIST;
            }
            @Override
            public Collection getRunningTasks() {
                return Collections.unmodifiableCollection(previouslyAssignedTasksMap.values());
            }
        };
    }

    private VirtualMachineCurrentState vmCurrentState() {
        return new VirtualMachineCurrentState() {
            @Override
            public String getHostname() {
                return hostname;
            }
            @Override
            public VirtualMachineLease getCurrAvailableResources() {
                return currTotalLease;
            }
            @Override
            public Collection getTasksCurrentlyAssigned() {
                return Collections.unmodifiableCollection(assignmentResults.values());
            }
            @Override
            public Collection getRunningTasks() {
                return Collections.unmodifiableCollection(previouslyAssignedTasksMap.values());
            }
        };
    }

    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();
    }

    void assignResult(TaskAssignmentResult result) {
        currUsedCpus += result.getRequest().getCPUs();
        currUsedMemory += result.getRequest().getMemory();
        currUsedNetworkMbps += result.getRequest().getNetworkMbps();
        for(int p=0; p© 2015 - 2025 Weber Informatics LLC | Privacy Policy