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

com.netflix.fenzo.AssignableVMs 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.functions.Func1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

class AssignableVMs {

    static class VMRejectLimiter {
        private long lastRejectAt=0;
        private int rejectedCount;
        private final int limit;
        private final long rejectDelay;

        VMRejectLimiter(int limit, long leaseOfferExpirySecs) {
            this.limit = limit;
            this.rejectDelay = leaseOfferExpirySecs*1000L;
        }
        synchronized boolean reject() {
            if(rejectedCount==limit)
                return false;
            rejectedCount++;
            lastRejectAt = System.currentTimeMillis();
            return true;
        }
        boolean limitReached() {
            return rejectedCount == limit;
        }
        private void reset() {
            if(System.currentTimeMillis() > (lastRejectAt + rejectDelay))
                rejectedCount=0;
        }
    }

    private static class HostDisablePair {
        private final String host;
        private final Long until;

        HostDisablePair(String host, Long until) {
            this.host = host;
            this.until = until;
        }
    }

    private final VMCollection vmCollection;
    private static final Logger logger = LoggerFactory.getLogger(AssignableVMs.class);
    private final ConcurrentMap leaseIdToHostnameMap = new ConcurrentHashMap<>();
    private final ConcurrentMap vmIdToHostnameMap = new ConcurrentHashMap<>();
    private final BlockingQueue disableRequests = new LinkedBlockingQueue<>();
    private final TaskTracker taskTracker;
    private final String attrNameToGroupMaxResources;
    private final Map> maxResourcesMap;
    private final Map totalResourcesMap;
    private final VMRejectLimiter vmRejectLimiter;
    private final AssignableVirtualMachine dummyVM = new AssignableVirtualMachine(null, null, "", null, 0L, null) {
        @Override
        void assignResult(TaskAssignmentResult result) {
            throw new UnsupportedOperationException();
        }
    };
    private final ActiveVmGroups activeVmGroups;
    private String activeVmGroupAttributeName=null;
    private final List unknownLeaseIdsToExpire = new ArrayList<>();

    AssignableVMs(TaskTracker taskTracker, Action1 leaseRejectAction,
                  long leaseOfferExpirySecs, int maxOffersToReject,
                  String attrNameToGroupMaxResources, boolean singleLeaseMode, String autoScaleByAttributeName) {
        this.taskTracker = taskTracker;
        vmCollection = new VMCollection(
                hostname -> new AssignableVirtualMachine(vmIdToHostnameMap, leaseIdToHostnameMap, hostname,
                        leaseRejectAction, leaseOfferExpirySecs, taskTracker, singleLeaseMode),
                autoScaleByAttributeName
        );
        this.attrNameToGroupMaxResources = attrNameToGroupMaxResources;
        maxResourcesMap = new HashMap<>();
        totalResourcesMap = new HashMap<>();
        vmRejectLimiter = new VMRejectLimiter(maxOffersToReject, leaseOfferExpirySecs);  // ToDo make this configurable?
        activeVmGroups = new ActiveVmGroups();
    }

    VMCollection getVmCollection() {
        return vmCollection;
    }

    Map> createPseudoHosts(Map groupCounts, Func1 ruleGetter) {
        return vmCollection.clonePseudoVMsForGroups(groupCounts, ruleGetter, lease ->
            lease != null &&
                    (lease.getAttributeMap() == null ||
                            lease.getAttributeMap().get(activeVmGroupAttributeName) == null ||
                            isInActiveVmGroup(lease.getAttributeMap().get(activeVmGroupAttributeName).getText().getValue())
                    )
        );
    }

    void removePseudoHosts(Map> hostsMap) {
        if (hostsMap != null && !hostsMap.isEmpty()) {
            for (Map.Entry> entry: hostsMap.entrySet()) {
                for (String h: entry.getValue()) {
                    final AssignableVirtualMachine avm = vmCollection.unsafeRemoveVm(h, entry.getKey());
                    if (avm != null)
                        avm.removeExpiredLeases(true, false);
                }
            }
        }
    }

    Map> getResourceStatus() {
        Map> result = new HashMap<>();
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs())
            result.put(avm.getHostname(), avm.getResourceStatus());
        return result;
    }

    void setTaskAssigned(TaskRequest request, String host) {
        vmCollection.getOrCreate(host).setAssignedTask(request);
    }

    void unAssignTask(String taskId, String host) {
        final Optional vmByName = vmCollection.getVmByName(host);
        if(vmByName.isPresent()) {
            vmByName.get().markTaskForUnassigning(taskId);
        }
        else
            logger.warn("No VM for host " + host + " to unassign task " + taskId);
    }

    private int addLeases(List leases) {
        if(logger.isDebugEnabled())
            logger.debug("Adding leases");
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs())
            avm.resetResources();
        int rejected=0;
        for(VirtualMachineLease l: leases) {
            if(vmCollection.addLease(l))
                rejected++;
        }
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs()) {
            if(logger.isDebugEnabled())
                logger.debug("Updating total lease on " + avm.getHostname());
            avm.updateCurrTotalLease();
            final VirtualMachineLease currTotalLease = avm.getCurrTotalLease();
            if(logger.isDebugEnabled()) {
                if (currTotalLease == null)
                    logger.debug("Updated total lease is null for " + avm.getHostname());
                else {
                    logger.debug("Updated total lease for {} has cpu={}, mem={}, disk={}, network={}",
                            avm.getHostname(), currTotalLease.cpuCores(), currTotalLease.memoryMB(),
                            currTotalLease.diskMB(), currTotalLease.networkMbps()
                    );
                }
            }
        }
        return rejected;
    }

    void expireLease(String leaseId) {
        final String hostname = leaseIdToHostnameMap.get(leaseId);
        if(hostname==null) {
            unknownLeaseIdsToExpire.add(leaseId);
            return;
        }
        internalExpireLease(leaseId, hostname);
    }

    private void internalExpireLease(String leaseId, String hostname) {
        final Optional vmByName = vmCollection.getVmByName(hostname);
        if(vmByName.isPresent()) {
            if(logger.isDebugEnabled())
                logger.debug("Expiring lease offer id " + leaseId + " on host " + hostname);
            vmByName.get().expireLease(leaseId);
        }
    }

    void expireAllLeases(String hostname) {
        final Optional vmByName = vmCollection.getVmByName(hostname);
        if(vmByName.isPresent())
            vmByName.get().expireAllLeases();
    }

    void expireAllLeases() {
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs())
            avm.expireAllLeases();
    }

    void disableUntil(String host, long until) {
        disableRequests.offer(new HostDisablePair(host, until));
    }

    private void disableVMs() {
        if (disableRequests.peek() == null)
            return;
        List disablePairs = new LinkedList<>();
        disableRequests.drainTo(disablePairs);
        for (HostDisablePair hostDisablePair: disablePairs) {
            final Optional vmByName = vmCollection.getVmByName(hostDisablePair.host);
            if (vmByName.isPresent())
                vmByName.get().setDisabledUntil(hostDisablePair.until);
        }
    }

    void enableVM(String host) {
        final Optional vmByName = vmCollection.getVmByName(host);
        if(vmByName.isPresent())
            vmByName.get().enable();
        else
            logger.warn("Can't enable host " + host + ", no such host");
    }

    String getHostnameFromVMId(String vmId) {
        return vmIdToHostnameMap.get(vmId);
    }

    void setActiveVmGroupAttributeName(String attributeName) {
        this.activeVmGroupAttributeName = attributeName;
    }

    void setActiveVmGroups(List vmGroups) {
        activeVmGroups.setActiveVmGroups(vmGroups);
    }

    private boolean isInActiveVmGroup(AssignableVirtualMachine avm) {
        final String attrValue = avm.getAttrValue(activeVmGroupAttributeName);
        return isInActiveVmGroup(attrValue);
    }

    private boolean isInActiveVmGroup(String attrValue) {
        return activeVmGroups.isActiveVmGroup(attrValue, false);
    }

    private void expireAnyUnknownLeaseIds() {
        if(!unknownLeaseIdsToExpire.isEmpty()) {
            for(String leaseId: unknownLeaseIdsToExpire) {
                final String hostname = leaseIdToHostnameMap.get(leaseId);
                if(hostname!=null)
                    internalExpireLease(leaseId, hostname);
            }
            unknownLeaseIdsToExpire.clear();
        }
    }

    List prepareAndGetOrderedVMs(List newLeases, AtomicInteger rejectedCount) {
        disableVMs();
        removeExpiredLeases();
        rejectedCount.addAndGet(addLeases(newLeases));
        expireAnyUnknownLeaseIds();
        List vms = new ArrayList<>();
        taskTracker.clearAssignedTasks();
        vmRejectLimiter.reset();
        resetTotalResources();
        // ToDo make this parallel maybe?
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs()) {
            avm.prepareForScheduling();
            if(isInActiveVmGroup(avm) && avm.isAssignableNow()) {
                // for now, only add it if it is available right now
                if(logger.isDebugEnabled())
                    logger.debug("Host " + avm.getHostname() + " available for assignments");
                vms.add(avm);
            }
            else if(logger.isDebugEnabled())
                logger.debug("Host " + avm.getHostname() + " not available for assignments");
            saveMaxResources(avm);
            if (isInActiveVmGroup(avm) && !avm.isDisabled())
                addTotalResources(avm);
        }
        taskTracker.setTotalResources(totalResourcesMap);
        //Collections.sort(vms);
        return vms;
    }

    List getInactiveVMs() {
        return vmCollection.getAllVMs().stream().filter(avm -> !isInActiveVmGroup(avm)).collect(Collectors.toList());
    }

    private void resetTotalResources() {
        totalResourcesMap.clear();
    }

    private void addTotalResources(AssignableVirtualMachine avm) {
        final Map maxResources = avm.getMaxResources();
        for (VMResource r: maxResources.keySet()) {
            Double v = maxResources.get(r);
            if (v != null) {
                if (totalResourcesMap.get(r) == null)
                    totalResourcesMap.put(r, v);
                else
                    totalResourcesMap.put(r, totalResourcesMap.get(r) + v);
            }
        }
    }

    private void removeExpiredLeases() {
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs())
            avm.removeExpiredLeases(!isInActiveVmGroup(avm));
    }

    int removeLimitedLeases(List idleResourcesList) {
        int rejected=0;
        List randomized = new ArrayList<>(idleResourcesList);
        // randomize the list so we don't always reject leases of the same VM before hitting the reject limit
        Collections.shuffle(randomized);
        for(VirtualMachineLease lease: randomized) {
            if(vmRejectLimiter.limitReached())
                break;
            final Optional vmByName = vmCollection.getVmByName(lease.hostname());
            if (vmByName.isPresent())
                rejected += vmByName.get().expireLimitedLeases(vmRejectLimiter);
        }
        return rejected;
    }

    int getTotalNumVMs() {
        return vmCollection.size();
    }

    void purgeInactiveVMs(Set excludeVms) {
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs()) {
            if(avm != null) {
                if (!excludeVms.contains(avm.getHostname())) {
                    if (!avm.isActive()) {
                        vmCollection.remove(avm);
                        if (avm.getCurrVMId() != null)
                            vmIdToHostnameMap.remove(avm.getCurrVMId(), avm.getHostname());
                        logger.info("Removed inactive host " + avm.getHostname());
                    }
                }
            }
        }
    }

    private void saveMaxResources(AssignableVirtualMachine avm) {
        if(attrNameToGroupMaxResources!=null && !attrNameToGroupMaxResources.isEmpty()) {
            String attrValue = avm.getAttrValue(attrNameToGroupMaxResources);
            if(attrValue !=null) {
                Map maxResources = avm.getMaxResources();
                Map savedMaxResources = maxResourcesMap.get(attrValue);
                if(savedMaxResources==null) {
                    savedMaxResources = new HashMap<>();
                    maxResourcesMap.put(attrValue, savedMaxResources);
                }
                for(VMResource r: VMResource.values()) {
                    switch (r) {
                        case CPU:
                        case Disk:
                        case Memory:
                        case Ports:
                        case Network:
                            Double savedVal = savedMaxResources.get(r)==null? 0.0 : savedMaxResources.get(r);
                            savedMaxResources.put(r, Math.max(savedVal, maxResources.get(r)));
                    }
                }
            }
        }
    }

    Map getMaxResources(String attrValue) {
        return maxResourcesMap.get(attrValue);
    }

    AssignmentFailure getFailedMaxResource(String attrValue, TaskRequest task) {
        AssignmentFailure savedFailure = null;
        for(Map.Entry> entry: maxResourcesMap.entrySet()) {
            if(attrValue!=null && !attrValue.equals(entry.getKey()))
                continue;
            final Map maxResources = entry.getValue();
            AssignmentFailure failure = null;
            for(VMResource res: VMResource.values()) {
                switch (res) {
                    case CPU:
                        if(maxResources.get(VMResource.CPU) < task.getCPUs()) {
                            failure = new AssignmentFailure(
                                    VMResource.CPU, task.getCPUs(), 0.0, maxResources.get(VMResource.CPU), "");
                        }
                        break;
                    case Memory:
                        if(maxResources.get(VMResource.Memory) < task.getMemory())
                            failure = new AssignmentFailure(
                                    VMResource.Memory, task.getMemory(), 0.0, maxResources.get(VMResource.Memory), "");
                        break;
                    case Disk:
                        if(maxResources.get(VMResource.Disk) < task.getDisk())
                            failure = new AssignmentFailure(
                                    VMResource.Disk, task.getDisk(), 0.0, maxResources.get(VMResource.Disk), "");
                        break;
                    case Ports:
                        if(maxResources.get(VMResource.Ports) < task.getPorts())
                            failure = new AssignmentFailure(
                                    VMResource.Ports, task.getPorts(), 0.0, maxResources.get(VMResource.Ports), "");
                        break;
                    case Network:
                        if(maxResources.get(VMResource.Network) < task.getNetworkMbps())
                            failure = new AssignmentFailure(
                                    VMResource.Network, task.getNetworkMbps(), 0.0, maxResources.get(VMResource.Network), "");
                        break;
                    case VirtualMachine:
                    case Fitness:
                    case ResAllocs:
                    case ResourceSet:
                    case Other:
                        break;
                    default:
                        logger.error("Unknown resource type: " + res);
                }
                if(failure!=null)
                    break;
            }
            if(failure == null)
                return null;
            savedFailure = failure;
        }
        return savedFailure;
    }

    ActiveVmGroups getActiveVmGroups() {
        return activeVmGroups;
    }

    List getVmCurrentStates() {
        List result = new ArrayList<>();
        for(AssignableVirtualMachine avm: vmCollection.getAllVMs())
            result.add(avm.getVmCurrentState());
        return result;
    }

    AssignableVirtualMachine getDummyVM() {
        return dummyVM;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy