From b0298283ae554ffdbc90ca0d9aa0f7f1668513c4 Mon Sep 17 00:00:00 2001 From: Corin Dwyer Date: Tue, 30 Apr 2019 08:13:56 -0700 Subject: [PATCH 1/3] remove sudo param from travisci config; closes #179 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c3124e9..cd8c3ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: java jdk: - oraclejdk8 -sudo: false install: ./installViaTravis.sh script: ./buildViaTravis.sh env: From d192cfe7395205ba36d43b40fed220df833e4ce2 Mon Sep 17 00:00:00 2001 From: Corin Dwyer Date: Tue, 30 Apr 2019 08:14:20 -0700 Subject: [PATCH 2/3] upgrade gradle wrapper and netflixoss plugin --- build.gradle | 6 +++++- gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 54333 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 1954c56..02cda80 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ buildscript { } } dependencies { - classpath 'com.netflix.nebula:gradle-netflixoss-project-plugin:3.5.2' + classpath 'com.netflix.nebula:gradle-netflixoss-project-plugin:5.0.0' } } @@ -58,4 +58,8 @@ subprojects { tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') } + + test { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c97a8bdb9088d370da7e88784a7a093b971aa23a..c44b679acd3f794ddbb3aa5e919244914911014a 100644 GIT binary patch delta 44903 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 {=lA)jDE{E-O`sK*BVTaBEU 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 z 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 01abdbe..69e3a96 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 12 15:36:03 PDT 2015 +#Thu May 17 12:49:38 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip From e5912330f22f4bab134829e26b1e05085ff595e4 Mon Sep 17 00:00:00 2001 From: Corin Dwyer Date: Tue, 30 Apr 2019 08:16:04 -0700 Subject: [PATCH 3/3] change info log statements to debug add experimental api for evaluating assignable VMs fix javadoc for disabling VMs; closes 178 --- .../java/com/netflix/fenzo/AssignableVMs.java | 2 +- .../fenzo/AssignableVirtualMachine.java | 2 +- .../java/com/netflix/fenzo/TaskScheduler.java | 465 ++++++++++-------- .../netflix/fenzo/TaskSchedulingService.java | 7 +- .../com/netflix/fenzo/queues/tiered/Tier.java | 2 +- .../fenzo/BinPackingSchedulerTests.java | 7 +- .../fenzo/TaskSchedulingServiceTest.java | 326 ++++++------ 7 files changed, 416 insertions(+), 395 deletions(-) diff --git a/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVMs.java b/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVMs.java index 81bf959..d462379 100644 --- a/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVMs.java +++ b/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVMs.java @@ -346,7 +346,7 @@ void purgeInactiveVMs(Set excludeVms) { vmCollection.remove(avm); if (avm.getCurrVMId() != null) vmIdToHostnameMap.remove(avm.getCurrVMId(), avm.getHostname()); - logger.info("Removed inactive host " + avm.getHostname()); + logger.debug("Removed inactive host " + avm.getHostname()); } } } diff --git a/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVirtualMachine.java b/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVirtualMachine.java index 1637c6f..efb1a94 100644 --- a/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVirtualMachine.java +++ b/fenzo-core/src/main/java/com/netflix/fenzo/AssignableVirtualMachine.java @@ -492,7 +492,7 @@ private void assignResourceSets(TaskRequest request) { } void expireLease(String leaseId) { - logger.info("Got request to expire lease on " + hostname); + logger.debug("Got request to expire lease on " + hostname); leasesToExpire.offer(leaseId); } diff --git a/fenzo-core/src/main/java/com/netflix/fenzo/TaskScheduler.java b/fenzo-core/src/main/java/com/netflix/fenzo/TaskScheduler.java index 3483558..2b4d93b 100644 --- a/fenzo-core/src/main/java/com/netflix/fenzo/TaskScheduler.java +++ b/fenzo-core/src/main/java/com/netflix/fenzo/TaskScheduler.java @@ -17,20 +17,17 @@ package com.netflix.fenzo; import com.netflix.fenzo.common.ThreadFactoryBuilder; -import com.netflix.fenzo.plugins.NoOpScaleDownOrderEvaluator; -import com.netflix.fenzo.queues.Assignable; -import com.netflix.fenzo.queues.QueuableTask; -import com.netflix.fenzo.sla.ResAllocs; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.netflix.fenzo.functions.Action1; -import com.netflix.fenzo.functions.Action2; -import com.netflix.fenzo.functions.Func1; - -import java.util.*; +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.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -41,6 +38,16 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import com.netflix.fenzo.functions.Action1; +import com.netflix.fenzo.functions.Action2; +import com.netflix.fenzo.functions.Func1; +import com.netflix.fenzo.plugins.NoOpScaleDownOrderEvaluator; +import com.netflix.fenzo.queues.Assignable; +import com.netflix.fenzo.queues.QueuableTask; +import com.netflix.fenzo.sla.ResAllocs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A scheduling service that you can use to optimize the assignment of tasks to hosts within a Mesos framework. * Call the {@link #scheduleOnce scheduleOnce()} method with a list of task requests and a list of new resource @@ -58,7 +65,7 @@ * {@link #getTaskUnAssigner getTaskUnAssigner()} method. These actions make the {@code TaskScheduler} keep * track of launched tasks. The {@code TaskScheduler} then makes these tracked tasks available to its * scheduling optimization functions. - * + *

* Do not call the scheduler concurrently. The scheduler assigns tasks in the order that they are received in a * particular list. It checks each task against available resources until it finds a match. *

@@ -79,34 +86,30 @@ public class TaskScheduler { */ public final static class Builder { - private Action1 leaseRejectAction=null; - private long leaseOfferExpirySecs=120; - private int maxOffersToReject=4; - private boolean rejectAllExpiredOffers=false; + private Action1 leaseRejectAction = null; + private long leaseOfferExpirySecs = 120; + private int maxOffersToReject = 4; + private boolean rejectAllExpiredOffers = false; private VMTaskFitnessCalculator fitnessCalculator = new DefaultFitnessCalculator(); - private String autoScaleByAttributeName=null; - private String autoScalerMapHostnameAttributeName=null; - private String autoScaleDownBalancedByAttributeName=null; + private String autoScaleByAttributeName = null; + private String autoScalerMapHostnameAttributeName = null; + private String autoScaleDownBalancedByAttributeName = null; private ScaleDownOrderEvaluator scaleDownOrderEvaluator; private Map weightedScaleDownConstraintEvaluators; private PreferentialNamedConsumableResourceEvaluator preferentialNamedConsumableResourceEvaluator = new DefaultPreferentialNamedConsumableResourceEvaluator(); - private Action1 autoscalerCallback=null; - private long delayAutoscaleUpBySecs=0L; - private long delayAutoscaleDownBySecs=0L; - private long disabledVmDurationInSecs =0L; - private List autoScaleRules=new ArrayList<>(); - private Func1 isFitnessGoodEnoughFunction = new Func1() { - @Override - public Boolean call(Double f) { - return f>1.0; - } - }; - private boolean disableShortfallEvaluation=false; - private Map resAllocs=null; - private boolean singleOfferMode=false; + private Action1 autoscalerCallback = null; + private long delayAutoscaleUpBySecs = 0L; + private long delayAutoscaleDownBySecs = 0L; + private long disabledVmDurationInSecs = 0L; + private List autoScaleRules = new ArrayList<>(); + private Func1 isFitnessGoodEnoughFunction = f -> f > 1.0; + private boolean disableShortfallEvaluation = false; + private Map resAllocs = null; + private boolean singleOfferMode = false; private final List schedulingEventListeners = new ArrayList<>(); private int maxConcurrent = Runtime.getRuntime().availableProcessors(); private Supplier taskBatchSizeSupplier = () -> Long.MAX_VALUE; + private Func1, List> assignableVMsEvaluator = null; /** * (Required) Call this method to establish a method that your task scheduler will call to notify you @@ -140,12 +143,14 @@ public Builder withLeaseOfferExpirySecs(long leaseOfferExpirySecs) { /** * Call this method to set the maximum number of offers to reject within a time period equal to lease expiry * seconds, set with {@code leaseOfferExpirySecs()}. Default is 4. + * * @param maxOffersToReject Maximum number of offers to reject. * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} */ public Builder withMaxOffersToReject(int maxOffersToReject) { - if(!rejectAllExpiredOffers) + if (!rejectAllExpiredOffers) { this.maxOffersToReject = maxOffersToReject; + } return this; } @@ -153,6 +158,7 @@ public Builder withMaxOffersToReject(int maxOffersToReject) { * Indicate that all offers older than the set expiry time must be rejected. By default this is set to false. * If false, Fenzo rejects a maximum number of offers set using {@link #withMaxOffersToReject(int)} per each * time period spanning the expiry time, set by {@link #withLeaseOfferExpirySecs(long)}. + * * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} */ public Builder withRejectAllExpiredOffers() { @@ -303,7 +309,7 @@ public Builder disableShortfallEvaluation() { * @param resAllocs a Map with the task group name as keys and resource allocation limits as values * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} * @see Resource Allocation - * Limits + * Limits */ public Builder withInitialResAllocs(Map resAllocs) { this.resAllocs = resAllocs; @@ -326,12 +332,15 @@ public Builder withInitialResAllocs(Map resAllocs) { * @see Autoscaling */ public Builder withAutoScaleRule(AutoScaleRule rule) { - if(autoScaleByAttributeName==null || autoScaleByAttributeName.isEmpty()) + if (autoScaleByAttributeName == null || autoScaleByAttributeName.isEmpty()) { throw new IllegalArgumentException("Auto scale by attribute name must be set before setting rules"); - if(rule.getMinIdleHostsToKeep()<1) + } + if (rule.getMinIdleHostsToKeep() < 1) { throw new IllegalArgumentException("Min Idle must be >0"); - if(rule.getMinIdleHostsToKeep()>rule.getMaxIdleHostsToKeep()) + } + if (rule.getMinIdleHostsToKeep() > rule.getMaxIdleHostsToKeep()) { throw new IllegalArgumentException("Min Idle must be <= Max Idle hosts"); + } this.autoScaleRules.add(rule); return this; } @@ -353,22 +362,24 @@ public Builder withAutoScalerCallback(Action1 callback) { * policy rules. Such scale ups can be caused by, for example, the periodic offer rejections that result in * offers coming back shortly. They can also be caused by certain environments where tasks are first scheduled * to replace existing tasks. - *

+ *

* The autoscaler takes the scale up action based on the latest scale up request value after the delay. - *

+ *

* The default is 0 secs. Ideally, you should set this to be at least two times the larger of the two values: *

    - *
  • Delay between successive calls to {@link TaskScheduler#scheduleOnce(List, List)}.
  • - *
  • Delay in get a rejected offer back from Mesos.
  • + *
  • Delay between successive calls to {@link TaskScheduler#scheduleOnce(List, List)}.
  • + *
  • Delay in get a rejected offer back from Mesos.
  • *
+ * * @param delayAutoscaleUpBySecs Delay autoscale up actions by this many seconds. * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} * @throws IllegalArgumentException if you give negative number for {@code delayAutoscalerbySecs}. * @see Autoscaling */ public Builder withDelayAutoscaleUpBySecs(long delayAutoscaleUpBySecs) { - if(delayAutoscaleUpBySecs < 0L) + if (delayAutoscaleUpBySecs < 0L) { throw new IllegalArgumentException("Delay secs can't be negative: " + delayAutoscaleUpBySecs); + } this.delayAutoscaleUpBySecs = delayAutoscaleUpBySecs; return this; } @@ -377,19 +388,21 @@ public Builder withDelayAutoscaleUpBySecs(long delayAutoscaleUpBySecs) { * Delay the autoscale down actions to reduce unnecessary actions due to short periods of breach of scale down * policy rules. Such scale downs can be caused by, for example, certain environments where existing tasks are * removed before replacing them with new tasks. - *

+ *

* The autoscaler takes the scale down action based on the latest scale down request value after the delay. - *

+ *

* The default is 0 secs. Ideally, you should set this to be at least two times the delay before terminated * tasks are replaced successfully. + * * @param delayAutoscaleDownBySecs Delay autoscale down actions by this many seconds. * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} * @throws IllegalArgumentException if you give negative number for {@code delayAutoscalerbySecs}. * @see Autoscaling */ public Builder withDelayAutoscaleDownBySecs(long delayAutoscaleDownBySecs) { - if(delayAutoscaleDownBySecs < 0L) + if (delayAutoscaleDownBySecs < 0L) { throw new IllegalArgumentException("Delay secs can't be negative: " + delayAutoscaleDownBySecs); + } this.delayAutoscaleDownBySecs = delayAutoscaleDownBySecs; return this; } @@ -408,7 +421,7 @@ public Builder withDelayAutoscaleDownBySecs(long delayAutoscaleDownBySecs) { * @see Autoscaling */ public Builder withAutoscaleDisabledVmDurationInSecs(long disabledVmDurationInSecs) { - if(disabledVmDurationInSecs <= 0L) { + if (disabledVmDurationInSecs <= 0L) { throw new IllegalArgumentException("disabledVmDurationInSecs must be greater than 0: " + disabledVmDurationInSecs); } this.disabledVmDurationInSecs = disabledVmDurationInSecs; @@ -460,18 +473,33 @@ public Builder withTaskBatchSizeSupplier(Supplier taskBatchSizeSupplier) { return this; } + /** + * A provided function that can transform the assignable virtual machines that will be used during a scheduling + * iteration right before the scheduling iteration happens. This function is useful for global filtering and sorting + * right before the VMs are used to make scheduling decisions. Since this function blocks the scheduling loop, the expectation + * is that it returns very quickly. + * This API is experimental and subject to change. + * + * @param function that takes in a list of the current VMs and returns a list with VMs. + * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskScheduler} + */ + public Builder withAssignableVMsEvaluator(Func1, List> function) { + this.assignableVMsEvaluator = function; + return this; + } + /** * Creates a {@link TaskScheduler} based on the various builder methods you have chained. * * @return a {@code TaskScheduler} built according to the specifications you indicated */ public TaskScheduler build() { - if(scaleDownOrderEvaluator == null) { - if(weightedScaleDownConstraintEvaluators != null) { + if (scaleDownOrderEvaluator == null) { + if (weightedScaleDownConstraintEvaluators != null) { scaleDownOrderEvaluator = new NoOpScaleDownOrderEvaluator(); } } else { - if(weightedScaleDownConstraintEvaluators == null) { + if (weightedScaleDownConstraintEvaluators == null) { weightedScaleDownConstraintEvaluators = Collections.emptyMap(); } } @@ -497,7 +525,7 @@ private EvalResult(List assignmentResults, TaskAssignmentR private final AssignableVMs assignableVMs; private static final Logger logger = LoggerFactory.getLogger(TaskScheduler.class); private static final long purgeVMsIntervalSecs = 60; - private long lastVMPurgeAt=System.currentTimeMillis(); + private long lastVMPurgeAt = System.currentTimeMillis(); private final Builder builder; private final StateMonitor stateMonitor; private final SchedulingEventListener schedulingEventListener; @@ -509,10 +537,12 @@ private EvalResult(List assignmentResults, TaskAssignmentR private final TaskTracker taskTracker; private volatile boolean usingSchedulingService = false; private final String usingSchedSvcMesg = "Invalid call when using task scheduling service"; + private final Func1, List> assignableVMsEvaluator; private TaskScheduler(Builder builder) { - if(builder.leaseRejectAction ==null) + if (builder.leaseRejectAction == null) { throw new IllegalArgumentException("Lease reject action must be non-null"); + } this.builder = builder; this.maxConcurrent = builder.maxConcurrent; ThreadFactory threadFactory = ThreadFactoryBuilder.newBuilder().withNameFormat("fenzo-worker-%d").build(); @@ -524,7 +554,7 @@ private TaskScheduler(Builder builder) { assignableVMs = new AssignableVMs(taskTracker, builder.leaseRejectAction, builder.preferentialNamedConsumableResourceEvaluator, builder.leaseOfferExpirySecs, builder.maxOffersToReject, builder.autoScaleByAttributeName, builder.singleOfferMode, builder.autoScaleByAttributeName); - if(builder.autoScaleByAttributeName != null && !builder.autoScaleByAttributeName.isEmpty()) { + if (builder.autoScaleByAttributeName != null && !builder.autoScaleByAttributeName.isEmpty()) { ScaleDownConstraintExecutor scaleDownConstraintExecutor = builder.scaleDownOrderEvaluator == null ? null : new ScaleDownConstraintExecutor(builder.scaleDownOrderEvaluator, builder.weightedScaleDownConstraintEvaluators); @@ -533,24 +563,28 @@ private TaskScheduler(Builder builder) { builder.autoScaleRules, assignableVMs, builder.disableShortfallEvaluation, assignableVMs.getActiveVmGroups(), assignableVMs.getVmCollection(), scaleDownConstraintExecutor); - if(builder.autoscalerCallback != null) + if (builder.autoscalerCallback != null) { autoScaler.setCallback(builder.autoscalerCallback); - if(builder.delayAutoscaleDownBySecs > 0L) + } + if (builder.delayAutoscaleDownBySecs > 0L) { autoScaler.setDelayScaleDownBySecs(builder.delayAutoscaleDownBySecs); - if(builder.delayAutoscaleUpBySecs > 0L) + } + if (builder.delayAutoscaleUpBySecs > 0L) { autoScaler.setDelayScaleUpBySecs(builder.delayAutoscaleUpBySecs); + } if (builder.disabledVmDurationInSecs > 0L) { autoScaler.setDisabledVmDurationInSecs(builder.disabledVmDurationInSecs); } + } else { + autoScaler = null; } - else { - autoScaler=null; - } + assignableVMsEvaluator = builder.assignableVMsEvaluator == null ? avms -> avms : builder.assignableVMsEvaluator; } void checkIfShutdown() throws IllegalStateException { - if(isShutdown.get()) + if (isShutdown.get()) { throw new IllegalStateException("TaskScheduler already shutdown"); + } } /** @@ -565,8 +599,9 @@ void checkIfShutdown() throws IllegalStateException { */ public void setAutoscalerCallback(Action1 callback) throws IllegalStateException { checkIfShutdown(); - if(autoScaler==null) + if (autoScaler == null) { throw new IllegalStateException("No autoScaler setup"); + } autoScaler.setCallback(callback); } @@ -575,14 +610,14 @@ public TaskTracker getTaskTracker() { } private TaskAssignmentResult getSuccessfulResult(List results) { - double bestFitness=0.0; - TaskAssignmentResult bestResult=null; - for(int r=results.size()-1; r>=0; r--) { + double bestFitness = 0.0; + TaskAssignmentResult bestResult = null; + for (int r = results.size() - 1; r >= 0; r--) { // change to using fitness value from assignment result TaskAssignmentResult res = results.get(r); - if(res!=null && res.isSuccessful()) { - if(bestResult==null || res.getFitness()>bestFitness || - (res.getFitness()==bestFitness && res.getHostname().compareTo(bestResult.getHostname())<0)) { + if (res != null && res.isSuccessful()) { + if (bestResult == null || res.getFitness() > bestFitness || + (res.getFitness() == bestFitness && res.getHostname().compareTo(bestResult.getHostname()) < 0)) { bestFitness = res.getFitness(); bestResult = res; } @@ -600,7 +635,7 @@ private boolean isGoodEnough(TaskAssignmentResult result) { * * @return current mapping of resource allocations * @see Resource Allocation - * Limits + * Limits */ public Map getResAllocs() { return resAllocsEvaluator.getResAllocs(); @@ -611,7 +646,7 @@ public Map getResAllocs() { * * @param resAllocs the resource allocation to add or replace * @see Resource Allocation - * Limits + * Limits */ public void addOrReplaceResAllocs(ResAllocs resAllocs) { resAllocsEvaluator.replaceResAllocs(resAllocs); @@ -622,7 +657,7 @@ public void addOrReplaceResAllocs(ResAllocs resAllocs) { * * @param groupName the name of the resource allocation to remove * @see Resource Allocation - * Limits + * Limits */ public void removeResAllocs(String groupName) { resAllocsEvaluator.remResAllocs(groupName); @@ -635,8 +670,9 @@ public void removeResAllocs(String groupName) { * @see Autoscaling */ public Collection getAutoScaleRules() { - if(autoScaler==null) + if (autoScaler == null) { return Collections.emptyList(); + } return autoScaler.getRules(); } @@ -666,8 +702,9 @@ public void removeAutoScaleRule(String ruleName) { } /* package */ void setTaskToClusterAutoScalerMapGetter(Func1> getter) { - if (autoScaler != null) + if (autoScaler != null) { autoScaler.setTaskToClustersGetter(getter); + } } /* package */ AutoScaler getAutoScaler() { @@ -706,65 +743,65 @@ public void removeAutoScaleRule(String ruleName) { * plugins. The scheduling routine stops upon catching any unexpected exceptions. These exceptions are surfaced to * you in one or both of two ways. *

    - *
  • The returned result object will contain the exceptions encountered in - * {@link SchedulingResult#getExceptions()}. In this case, no assignments would have been made.
  • - *
  • This method may throw {@code IllegalStateException} with its cause set to the uncaught exception. In this - * case the internal state of Fenzo will be undefined.
  • + *
  • The returned result object will contain the exceptions encountered in + * {@link SchedulingResult#getExceptions()}. In this case, no assignments would have been made.
  • + *
  • This method may throw {@code IllegalStateException} with its cause set to the uncaught exception. In this + * case the internal state of Fenzo will be undefined.
  • *
* If there are exceptions, the internal state of Fenzo may be corrupt with no way to undo any partial effects. * - * @param requests a list of task requests to match with resources, in their given order + * @param requests a list of task requests to match with resources, in their given order * @param newLeases new resource leases from hosts that the scheduler can use along with any previously * ununsed leases * @return a {@link SchedulingResult} object that contains a task assignment results map and other summaries * @throws IllegalStateException if you call this method concurrently, or, if you try to add an existing lease - * again, or, if there was unexpected exception during the scheduling iteration, or, if using - * {@link TaskSchedulingService}, which will instead invoke scheduling from within. Unexpected exceptions - * can arise from uncaught exceptions in user defined plugins. It is also thrown if the scheduler has been shutdown - * via the {@link #shutdown()} method. + * again, or, if there was unexpected exception during the scheduling iteration, or, if using + * {@link TaskSchedulingService}, which will instead invoke scheduling from within. Unexpected exceptions + * can arise from uncaught exceptions in user defined plugins. It is also thrown if the scheduler has been shutdown + * via the {@link #shutdown()} method. */ public SchedulingResult scheduleOnce( List requests, List newLeases) throws IllegalStateException { - if (usingSchedulingService) + if (usingSchedulingService) { throw new IllegalStateException(usingSchedSvcMesg); + } final Iterator iterator = requests != null ? requests.iterator() : Collections.emptyIterator(); - TaskIterator taskIterator = new TaskIterator() { - @Override - public Assignable next() { - if (iterator.hasNext()) - return Assignable.success(iterator.next()); - return null; + TaskIterator taskIterator = () -> { + if (iterator.hasNext()) { + return Assignable.success(iterator.next()); } + return null; }; return scheduleOnce(taskIterator, newLeases); } /** * Variant of {@link #scheduleOnce(List, List)} that takes a task iterator instead of task list. + * * @param taskIterator Iterator for tasks to assign resources to. - * @param newLeases new resource leases from hosts that the scheduler can use along with any previously - * ununsed leases + * @param newLeases new resource leases from hosts that the scheduler can use along with any previously + * ununsed leases * @return a {@link SchedulingResult} object that contains a task assignment results map and other summaries * @throws IllegalStateException if you call this method concurrently, or, if you try to add an existing lease - * again, or, if there was unexpected exception during the scheduling iteration. For example, unexpected exceptions - * can arise from uncaught exceptions in user defined plugins. It is also thrown if the scheduler has been shutdown - * via the {@link #shutdown()} method. + * again, or, if there was unexpected exception during the scheduling iteration. For example, unexpected exceptions + * can arise from uncaught exceptions in user defined plugins. It is also thrown if the scheduler has been shutdown + * via the {@link #shutdown()} method. */ /* package */ SchedulingResult scheduleOnce( TaskIterator taskIterator, List newLeases) throws IllegalStateException { checkIfShutdown(); - try (AutoCloseable ac = stateMonitor.enter()) { + try (AutoCloseable ignored = stateMonitor.enter()) { return doScheduling(taskIterator, newLeases); } catch (Exception e) { logger.error("Error with scheduling run: " + e.getMessage(), e); - if(e instanceof IllegalStateException) - throw (IllegalStateException)e; - else { + if (e instanceof IllegalStateException) { + throw (IllegalStateException) e; + } else { logger.warn("Unexpected exception: " + e.getMessage()); throw new IllegalStateException("Unexpected exception during scheduling run: " + e.getMessage(), e); } @@ -774,6 +811,7 @@ public Assignable next() { /** * Variant of {@link #scheduleOnce(List, List)} that should be only used to schedule a pseudo iteration as it * ignores the StateMonitor lock. + * * @param taskIterator Iterator for tasks to assign resources to. * @return a {@link SchedulingResult} object that contains a task assignment results map and other summaries */ @@ -785,11 +823,11 @@ private SchedulingResult doScheduling(TaskIterator taskIterator, List newLeases) throws Exception { long start = System.currentTimeMillis(); final SchedulingResult schedulingResult = doSchedule(taskIterator, newLeases); - if((lastVMPurgeAt + purgeVMsIntervalSecs*1000) < System.currentTimeMillis()) { + if ((lastVMPurgeAt + purgeVMsIntervalSecs * 1000) < System.currentTimeMillis()) { lastVMPurgeAt = System.currentTimeMillis(); - logger.info("Purging inactive VMs"); + logger.debug("Purging inactive VMs"); assignableVMs.purgeInactiveVMs( // explicitly exclude VMs that have assignments - schedulingResult.getResultMap() == null? + schedulingResult.getResultMap() == null ? Collections.emptySet() : new HashSet<>(schedulingResult.getResultMap().keySet()) ); @@ -802,25 +840,28 @@ private SchedulingResult doSchedule( TaskIterator taskIterator, List newLeases) throws Exception { AtomicInteger rejectedCount = new AtomicInteger(); - List avms = assignableVMs.prepareAndGetOrderedVMs(newLeases, rejectedCount); - if(logger.isDebugEnabled()) - logger.debug("Got {} avms", avms.size()); + List originalVms = assignableVMs.prepareAndGetOrderedVMs(newLeases, rejectedCount); + List avms = assignableVMsEvaluator.call(originalVms); + if (logger.isDebugEnabled()) { + logger.debug("Original VMs: {}", originalVms); + logger.debug("VMs: {}", avms); + } + List inactiveAVMs = assignableVMs.getInactiveVMs(); - if(logger.isDebugEnabled()) - logger.debug("Found {} VMs with non-zero offers to assign from", avms.size()); final boolean hasResAllocs = resAllocsEvaluator.prepare(); //logger.info("Got " + avms.size() + " AVMs to schedule on"); - int totalNumAllocations=0; + int totalNumAllocations = 0; Set failedTasksForAutoScaler = new HashSet<>(); Map resultMap = new HashMap<>(avms.size()); final SchedulingResult schedulingResult = new SchedulingResult(resultMap); long taskBatchSize = builder.taskBatchSizeSupplier.get(); long tasksIterationCount = 0; - if(avms.isEmpty()) { + if (avms.isEmpty()) { while (true) { final Assignable taskOrFailure = taskIterator.next(); - if (taskOrFailure == null) + if (taskOrFailure == null) { break; + } failedTasksForAutoScaler.add(taskOrFailure.getTask()); } } else { @@ -831,102 +872,106 @@ private SchedulingResult doSchedule( break; } final Assignable taskOrFailure = taskIterator.next(); - if(logger.isDebugEnabled()) - logger.debug("TaskSched: task=" + (taskOrFailure == null? "null" : taskOrFailure.getTask().getId())); - if (taskOrFailure == null) + if (logger.isDebugEnabled()) { + logger.debug("TaskSched: task=" + (taskOrFailure == null ? "null" : taskOrFailure.getTask().getId())); + } + if (taskOrFailure == null) { break; - if(taskOrFailure.hasFailure()) { + } + if (taskOrFailure.hasFailure()) { schedulingResult.addFailures( taskOrFailure.getTask(), Collections.singletonList(new TaskAssignmentResult( - assignableVMs.getDummyVM(), - taskOrFailure.getTask(), - false, - Collections.singletonList(taskOrFailure.getAssignmentFailure()), - null, - 0 - ) - )); + assignableVMs.getDummyVM(), + taskOrFailure.getTask(), + false, + Collections.singletonList(taskOrFailure.getAssignmentFailure()), + null, + 0 + ) + )); continue; } TaskRequest task = taskOrFailure.getTask(); failedTasksForAutoScaler.add(task); - if(hasResAllocs) { - if(resAllocsEvaluator.taskGroupFailed(task.taskGroupName())) { - if(logger.isDebugEnabled()) + if (hasResAllocs) { + if (resAllocsEvaluator.taskGroupFailed(task.taskGroupName())) { + if (logger.isDebugEnabled()) { logger.debug("Resource allocation limits reached for task: " + task.getId()); + } continue; } final AssignmentFailure resAllocsFailure = resAllocsEvaluator.hasResAllocs(task); - if(resAllocsFailure != null) { + if (resAllocsFailure != null) { final List failures = Collections.singletonList(new TaskAssignmentResult(assignableVMs.getDummyVM(), task, false, Collections.singletonList(resAllocsFailure), null, 0.0)); schedulingResult.addFailures(task, failures); failedTasksForAutoScaler.remove(task); // don't scale up for resAllocs failures - if(logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { logger.debug("Resource allocation limit reached for task " + task.getId() + ": " + resAllocsFailure); + } continue; } } final AssignmentFailure maxResourceFailure = assignableVMs.getFailedMaxResource(null, task); - if(maxResourceFailure != null) { + if (maxResourceFailure != null) { final List failures = Collections.singletonList(new TaskAssignmentResult(assignableVMs.getDummyVM(), task, false, Collections.singletonList(maxResourceFailure), null, 0.0)); schedulingResult.addFailures(task, failures); - if(logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { logger.debug("Task {}: maxResource failure: {}", task.getId(), maxResourceFailure); + } continue; } // create batches of VMs to evaluate assignments concurrently across the batches final BlockingQueue virtualMachines = new ArrayBlockingQueue<>(avms.size(), false, avms); - int nThreads = (int)Math.ceil((double)avms.size()/ PARALLEL_SCHED_EVAL_MIN_BATCH_SIZE); + int nThreads = (int) Math.ceil((double) avms.size() / PARALLEL_SCHED_EVAL_MIN_BATCH_SIZE); List> futures = new ArrayList<>(); - if(logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { logger.debug("Launching {} threads for evaluating assignments for task {}", nThreads, task.getId()); - for(int b = 0; b() { - @Override - public EvalResult call() throws Exception { - return evalAssignments(task, virtualMachines); - } - })); + } + for (int b = 0; b < nThreads && b < maxConcurrent; b++) { + futures.add(executorService.submit(() -> evalAssignments(task, virtualMachines))); } List results = new ArrayList<>(); List bestResults = new ArrayList<>(); - for(Future f: futures) { + for (Future f : futures) { try { EvalResult evalResult = f.get(); - if(evalResult.exception!=null) { + if (evalResult.exception != null) { logger.warn("Error during concurrent task assignment eval - " + evalResult.exception.getMessage(), evalResult.exception); schedulingResult.addException(evalResult.exception); - } - else { + } else { results.add(evalResult); bestResults.add(evalResult.result); - if(logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { logger.debug("Task {}: best result so far: {}", task.getId(), evalResult.result); + } totalNumAllocations += evalResult.numAllocationTrials; } - } catch (InterruptedException|ExecutionException e) { + } catch (InterruptedException | ExecutionException e) { logger.error("Unexpected during concurrent task assignment eval - " + e.getMessage(), e); } } - if(!schedulingResult.getExceptions().isEmpty()) + if (!schedulingResult.getExceptions().isEmpty()) { break; + } TaskAssignmentResult successfulResult = getSuccessfulResult(bestResults); List failures = new ArrayList<>(); - if(successfulResult == null) { - if(logger.isDebugEnabled()) + if (successfulResult == null) { + if (logger.isDebugEnabled()) { logger.debug("Task {}: no successful results", task.getId()); - for(EvalResult er: results) + } + for (EvalResult er : results) { failures.addAll(er.assignmentResults); + } schedulingResult.addFailures(task, failures); - } - else { - if(logger.isDebugEnabled()) + } else { + if (logger.isDebugEnabled()) { logger.debug("Task {}: found successful assignment on host {}", task.getId(), successfulResult.getHostname()); + } successfulResult.assignResult(); tasksIterationCount++; failedTasksForAutoScaler.remove(task); @@ -938,13 +983,14 @@ public EvalResult call() throws Exception { } } List idleResourcesList = new ArrayList<>(); - if(schedulingResult.getExceptions().isEmpty()) { + if (schedulingResult.getExceptions().isEmpty()) { List expirableLeases = new ArrayList<>(); for (AssignableVirtualMachine avm : avms) { VMAssignmentResult assignmentResult = avm.resetAndGetSuccessfullyAssignedRequests(); if (assignmentResult == null) { - if (!avm.hasPreviouslyAssignedTasks()) + if (!avm.hasPreviouslyAssignedTasks()) { idleResourcesList.add(avm.getCurrTotalLease()); + } expirableLeases.add(avm.getCurrTotalLease()); } else { resultMap.put(avm.getHostname(), assignmentResult); @@ -958,9 +1004,10 @@ public EvalResult call() throws Exception { .collect(Collectors.toList()); rejectedCount.addAndGet(assignableVMs.removeLimitedLeases(expirableLeases)); - final AutoScalerInput autoScalerInput = new AutoScalerInput(idleResourcesList, idleInactiveAVMs, failedTasksForAutoScaler); - if (autoScaler != null) + if (autoScaler != null) { + AutoScalerInput autoScalerInput = new AutoScalerInput(idleResourcesList, idleInactiveAVMs, failedTasksForAutoScaler); autoScaler.doAutoscale(autoScalerInput); + } } schedulingResult.setLeasesAdded(newLeases.size()); schedulingResult.setLeasesRejected(rejectedCount.get()); @@ -971,7 +1018,7 @@ public EvalResult call() throws Exception { } /* package */ Map> createPseudoHosts(Map groupCounts) { - return assignableVMs.createPseudoHosts(groupCounts, autoScaler == null? name -> null : autoScaler::getRule); + return assignableVMs.createPseudoHosts(groupCounts, autoScaler == null ? name -> null : autoScaler::getRule); } /* package */ void removePseudoHosts(Map> hostsMap) { @@ -988,21 +1035,22 @@ public EvalResult call() throws Exception { * information. Scheduling runs are blocked around the lock. * * @return a Map of state information with the hostname as the key and a Map of resource state as the value. - * The resource state Map contains a resource as the key and a two element Double array - the first - * element of which contains the amount of the resource used and the second element contains the - * amount still available (available does not include used). - * @see How to Learn Which Resources Are Available on Which Hosts + * The resource state Map contains a resource as the key and a two element Double array - the first + * element of which contains the amount of the resource used and the second element contains the + * amount still available (available does not include used). * @throws IllegalStateException if called concurrently with {@link #scheduleOnce(List, List)} or if called when - * using a {@link TaskSchedulingService}. + * using a {@link TaskSchedulingService}. + * @see How to Learn Which Resources Are Available on Which Hosts */ public Map> getResourceStatus() throws IllegalStateException { - if (usingSchedulingService) + if (usingSchedulingService) { throw new IllegalStateException(usingSchedSvcMesg); + } return getResourceStatusIntl(); } /* package */ Map> getResourceStatusIntl() { - try (AutoCloseable ac = stateMonitor.enter()) { + try (AutoCloseable ignored = stateMonitor.enter()) { return assignableVMs.getResourceStatus(); } catch (Exception e) { logger.error("Unexpected error from state monitor: " + e.getMessage()); @@ -1017,20 +1065,20 @@ public Map> getResourceStatus() throws Illegal * * @return a list containing the current state of all known VMs * @throws IllegalStateException if called concurrently with {@link #scheduleOnce(List, List)} or if called when - * using a {@link TaskSchedulingService}. + * using a {@link TaskSchedulingService}. * @see How to Learn the Amount of Resources Currently Available on Particular Hosts */ public List getVmCurrentStates() throws IllegalStateException { - if (usingSchedulingService) + if (usingSchedulingService) { throw new IllegalStateException(usingSchedSvcMesg); + } return getVmCurrentStatesIntl(); } /* package */ List getVmCurrentStatesIntl() throws IllegalStateException { - try (AutoCloseable ac = stateMonitor.enter()) { + try (AutoCloseable ignored = stateMonitor.enter()) { return assignableVMs.getVmCurrentStates(); - } - catch (Exception e) { + } catch (Exception e) { logger.error("Unexpected error from state monitor: " + e.getMessage(), e); throw new IllegalStateException(e); } @@ -1041,31 +1089,31 @@ private EvalResult evalAssignments(TaskRequest task, BlockingQueue buf = new ArrayList<>(N); List results = new ArrayList<>(); - while(true) { + while (true) { buf.clear(); int n = virtualMachines.drainTo(buf, N); - if(n == 0) + if (n == 0) { return new EvalResult(results, getSuccessfulResult(results), results.size(), null); - for(int m=0; m * In addition, in your framework's task completion callback that you supply to Mesos, you must call your * task scheduler's {@link #getTaskUnAssigner() getTaskUnassigner().call()} method to notify Fenzo that the @@ -1140,21 +1190,19 @@ public void expireAllLeases() throws IllegalStateException { * @throws IllegalStateException if the scheduler is shutdown via the {@link #isShutdown} method. */ public Action2 getTaskAssigner() throws IllegalStateException { - if (usingSchedulingService) + if (usingSchedulingService) { throw new IllegalStateException(usingSchedSvcMesg); + } return getTaskAssignerIntl(); } /* package */Action2 getTaskAssignerIntl() throws IllegalStateException { - return new Action2() { - @Override - public void call(TaskRequest request, String hostname) { - try (AutoCloseable ac = stateMonitor.enter()) { - assignableVMs.setTaskAssigned(request, hostname); - } catch (Exception e) { - logger.error("Unexpected error from state monitor: " + e.getMessage(), e); - throw new IllegalStateException(e); - } + return (request, hostname) -> { + try (AutoCloseable ignored = stateMonitor.enter()) { + assignableVMs.setTaskAssigned(request, hostname); + } catch (Exception e) { + logger.error("Unexpected error from state monitor: " + e.getMessage(), e); + throw new IllegalStateException(e); } }; } @@ -1184,46 +1232,38 @@ public void call(TaskRequest request, String hostname) { * @throws IllegalStateException if the scheduler is shutdown via the {@link #isShutdown} method. */ public Action2 getTaskUnAssigner() throws IllegalStateException { - return new Action2() { - @Override - public void call(String taskId, String hostname) { - assignableVMs.unAssignTask(taskId, hostname); - } - }; + return assignableVMs::unAssignTask; } /** - * Disable the virtual machine with the specified hostname. If the scheduler is not yet aware of the host - * with that hostname, it creates a new object for it, and therefore your disabling of it will be remembered - * when offers that concern that host come in later. The scheduler will not use disabled hosts for + * Disable the virtual machine with the specified hostname. The scheduler will not use disabled hosts for * allocating resources to tasks. * - * @param hostname the name of the host to disable + * @param hostname the name of the host to disable * @param durationMillis the length of time, starting from now, in milliseconds, during which the host will - * be disabled + * be disabled * @throws IllegalStateException if the scheduler is shutdown via the {@link #isShutdown} method. */ public void disableVM(String hostname, long durationMillis) throws IllegalStateException { - logger.info("Disable VM " + hostname + " for " + durationMillis + " millis"); - assignableVMs.disableUntil(hostname, System.currentTimeMillis()+durationMillis); + logger.debug("Disable VM " + hostname + " for " + durationMillis + " millis"); + assignableVMs.disableUntil(hostname, System.currentTimeMillis() + durationMillis); } /** - * Disable the virtual machine with the specified ID. If the scheduler is not yet aware of the host with - * that hostname, it creates a new object for it, and therefore your disabling of it will be remembered when - * offers that concern that host come in later. The scheduler will not use disabled hosts for allocating + * Disable the virtual machine with the specified ID. The scheduler will not use disabled hosts for allocating * resources to tasks. * - * @param vmID the ID of the host to disable + * @param vmID the ID of the host to disable * @param durationMillis the length of time, starting from now, in milliseconds, during which the host will - * be disabled + * be disabled * @return {@code true} if the ID matches a known VM, {@code false} otherwise. * @throws IllegalStateException if the scheduler is shutdown via the {@link #isShutdown} method. */ public boolean disableVMByVMId(String vmID, long durationMillis) throws IllegalStateException { final String hostname = assignableVMs.getHostnameFromVMId(vmID); - if(hostname == null) + if (hostname == null) { return false; + } disableVM(hostname, durationMillis); return true; } @@ -1236,7 +1276,7 @@ public boolean disableVMByVMId(String vmID, long durationMillis) throws IllegalS * @throws IllegalStateException if the scheduler is shutdown via the {@link #isShutdown} method. */ public void enableVM(String hostname) throws IllegalStateException { - logger.info("Enabling VM " + hostname); + logger.debug("Enabling VM " + hostname); assignableVMs.enableVM(hostname); } @@ -1258,7 +1298,7 @@ public void setActiveVmGroupAttributeName(String attributeName) { * to be enabled. * * @param vmGroups a list of VM group names that the scheduler is to consider to be enabled, or {@code null} - * if the scheduler is to consider every group to be enabled + * if the scheduler is to consider every group to be enabled */ public void setActiveVmGroups(List vmGroups) { assignableVMs.setActiveVmGroups(vmGroups); @@ -1268,10 +1308,11 @@ public void setActiveVmGroups(List vmGroups) { * Mark task scheduler as shutdown and shutdown any thread pool executors created. */ public void shutdown() { - if(isShutdown.compareAndSet(false, true)) { + if (isShutdown.compareAndSet(false, true)) { executorService.shutdown(); - if(autoScaler != null) + if (autoScaler != null) { autoScaler.shutdown(); + } } } } diff --git a/fenzo-core/src/main/java/com/netflix/fenzo/TaskSchedulingService.java b/fenzo-core/src/main/java/com/netflix/fenzo/TaskSchedulingService.java index 1f9c21f..49a254a 100644 --- a/fenzo-core/src/main/java/com/netflix/fenzo/TaskSchedulingService.java +++ b/fenzo-core/src/main/java/com/netflix/fenzo/TaskSchedulingService.java @@ -136,12 +136,7 @@ private TaskSchedulingService(Builder builder) { * new leases. */ public void start() { - executorService.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - TaskSchedulingService.this.scheduleOnce(); - } - }, 0, loopIntervalMillis, TimeUnit.MILLISECONDS); + executorService.scheduleWithFixedDelay(TaskSchedulingService.this::scheduleOnce, 0, loopIntervalMillis, TimeUnit.MILLISECONDS); } /** diff --git a/fenzo-core/src/main/java/com/netflix/fenzo/queues/tiered/Tier.java b/fenzo-core/src/main/java/com/netflix/fenzo/queues/tiered/Tier.java index 6fd2da5..a7beef6 100644 --- a/fenzo-core/src/main/java/com/netflix/fenzo/queues/tiered/Tier.java +++ b/fenzo-core/src/main/java/com/netflix/fenzo/queues/tiered/Tier.java @@ -300,7 +300,7 @@ public void setTotalResources(Map totalResourcesMap) { for (QueueBucket b : sortedBuckets.getSortedList()) { b.setTotalResources(tierResources); } - logger.info("Re-sorting buckets in tier " + tierNumber + " after totals changed"); + logger.debug("Re-sorting buckets in tier " + tierNumber + " after totals changed"); sortedBuckets.resort(); } } diff --git a/fenzo-core/src/test/java/com/netflix/fenzo/BinPackingSchedulerTests.java b/fenzo-core/src/test/java/com/netflix/fenzo/BinPackingSchedulerTests.java index 36b71a7..7f4d75a 100644 --- a/fenzo-core/src/test/java/com/netflix/fenzo/BinPackingSchedulerTests.java +++ b/fenzo-core/src/test/java/com/netflix/fenzo/BinPackingSchedulerTests.java @@ -45,12 +45,7 @@ private TaskScheduler getScheduler(VMTaskFitnessCalculator fitnessCalculator) { return new TaskScheduler.Builder() .withFitnessCalculator(fitnessCalculator) .withLeaseOfferExpirySecs(1000000) - .withLeaseRejectAction(new Action1() { - @Override - public void call(VirtualMachineLease virtualMachineLease) { - logger.info("Rejecting lease on " + virtualMachineLease.hostname()); - } - }) + .withLeaseRejectAction(virtualMachineLease -> logger.info("Rejecting lease on " + virtualMachineLease.hostname())) .build(); } diff --git a/fenzo-core/src/test/java/com/netflix/fenzo/TaskSchedulingServiceTest.java b/fenzo-core/src/test/java/com/netflix/fenzo/TaskSchedulingServiceTest.java index 7932ebb..5a29f2b 100644 --- a/fenzo-core/src/test/java/com/netflix/fenzo/TaskSchedulingServiceTest.java +++ b/fenzo-core/src/test/java/com/netflix/fenzo/TaskSchedulingServiceTest.java @@ -16,23 +16,35 @@ package com.netflix.fenzo; -import com.netflix.fenzo.functions.Action0; -import com.netflix.fenzo.functions.Action1; -import com.netflix.fenzo.plugins.BinPackingFitnessCalculators; -import com.netflix.fenzo.queues.*; -import com.netflix.fenzo.queues.tiered.QueuableTaskProvider; -import org.junit.Assert; -import org.junit.Test; - -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import com.netflix.fenzo.functions.Action0; +import com.netflix.fenzo.functions.Action1; +import com.netflix.fenzo.functions.Func1; +import com.netflix.fenzo.plugins.BinPackingFitnessCalculators; +import com.netflix.fenzo.queues.QAttributes; +import com.netflix.fenzo.queues.QueuableTask; +import com.netflix.fenzo.queues.TaskQueue; +import com.netflix.fenzo.queues.TaskQueueException; +import com.netflix.fenzo.queues.TaskQueueMultiException; +import com.netflix.fenzo.queues.TaskQueues; +import com.netflix.fenzo.queues.tiered.QueuableTaskProvider; +import org.junit.Assert; +import org.junit.Test; public class TaskSchedulingServiceTest { @@ -52,11 +64,8 @@ private TaskSchedulingService getSchedulingService(TaskQueue queue, TaskSchedule .withTaskQueue(queue) .withLoopIntervalMillis(loopMillis) .withMaxDelayMillis(maxDelayMillis) - .withPreSchedulingLoopHook(new Action0() { - @Override - public void call() { - //System.out.println("Pre-scheduling hook"); - } + .withPreSchedulingLoopHook(() -> { + //System.out.println("Pre-scheduling hook"); }) .withSchedulingResultCallback(resultCallback) .withTaskScheduler(scheduler) @@ -64,15 +73,15 @@ public void call() { } public TaskScheduler getScheduler() { + return getScheduler(avms -> avms); + } + + public TaskScheduler getScheduler(Func1, List> assignableVMsEvaluator) { return new TaskScheduler.Builder() .withLeaseOfferExpirySecs(1000000) - .withLeaseRejectAction(new Action1() { - @Override - public void call(VirtualMachineLease virtualMachineLease) { - System.out.println("Rejecting offer on host " + virtualMachineLease.hostname()); - } - }) + .withLeaseRejectAction(virtualMachineLease -> System.out.println("Rejecting offer on host " + virtualMachineLease.hostname())) .withFitnessCalculator(BinPackingFitnessCalculators.cpuMemBinPacker) + .withAssignableVMsEvaluator(assignableVMsEvaluator) .build(); } @@ -88,15 +97,12 @@ private void testOneTaskInternal(QueuableTask queuableTask, Action0 action) thro final CountDownLatch latch = new CountDownLatch(1); TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); - if (schedulingResult.getResultMap().size() > 0) { - //System.out.println("Assignment on host " + schedulingResult.getResultMap().values().iterator().next().getHostname()); - latch.countDown(); - scheduler.shutdown(); - } + Action1 resultCallback = schedulingResult -> { + //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); + if (schedulingResult.getResultMap().size() > 0) { + //System.out.println("Assignment on host " + schedulingResult.getResultMap().values().iterator().next().getHostname()); + latch.countDown(); + scheduler.shutdown(); } }; final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, 1000L, resultCallback); @@ -144,30 +150,27 @@ public void testMultipleTaskAssignments() throws Exception { final CountDownLatch latch = new CountDownLatch(numTasks); final TaskScheduler scheduler = getScheduler(); final AtomicReference ref = new AtomicReference<>(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); - if (!schedulingResult.getExceptions().isEmpty()) { - Assert.fail(schedulingResult.getExceptions().get(0).getMessage()); - } - else if (schedulingResult.getResultMap().size() > 0) { - final VMAssignmentResult vmAssignmentResult = schedulingResult.getResultMap().values().iterator().next(); + Action1 resultCallback = schedulingResult -> { + //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); + if (!schedulingResult.getExceptions().isEmpty()) { + Assert.fail(schedulingResult.getExceptions().get(0).getMessage()); + } + else if (schedulingResult.getResultMap().size() > 0) { + final VMAssignmentResult vmAssignmentResult = schedulingResult.getResultMap().values().iterator().next(); // System.out.println("Assignment on host " + vmAssignmentResult.getHostname() + // " with " + vmAssignmentResult.getTasksAssigned().size() + " tasks" // ); - for (TaskAssignmentResult r: vmAssignmentResult.getTasksAssigned()) { - latch.countDown(); - } - ref.get().addLeases( - Collections.singletonList(LeaseProvider.getConsumedLease(vmAssignmentResult)) - ); + for (TaskAssignmentResult r: vmAssignmentResult.getTasksAssigned()) { + latch.countDown(); } - else { - final Map> failures = schedulingResult.getFailures(); - if (!failures.isEmpty()) { - Assert.fail(failures.values().iterator().next().iterator().next().toString()); - } + ref.get().addLeases( + Collections.singletonList(LeaseProvider.getConsumedLease(vmAssignmentResult)) + ); + } + else { + final Map> failures = schedulingResult.getFailures(); + if (!failures.isEmpty()) { + Assert.fail(failures.values().iterator().next().iterator().next().toString()); } } }; @@ -189,25 +192,22 @@ public void testOrderedAssignments() throws Exception { TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); final BlockingQueue assignmentResults = new LinkedBlockingQueue<>(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - final Map resultMap = schedulingResult.getResultMap(); - if (!resultMap.isEmpty()) { - for (VMAssignmentResult r: resultMap.values()) { - for (TaskAssignmentResult t: r.getTasksAssigned()) { - assignmentResults.offer((QueuableTask)t.getRequest()); - //System.out.println("******* Assignment for task " + t.getTaskId()); - } + Action1 resultCallback = schedulingResult -> { + final Map resultMap = schedulingResult.getResultMap(); + if (!resultMap.isEmpty()) { + for (VMAssignmentResult r: resultMap.values()) { + for (TaskAssignmentResult t: r.getTasksAssigned()) { + assignmentResults.offer((QueuableTask)t.getRequest()); + //System.out.println("******* Assignment for task " + t.getTaskId()); } } + } // final Map> failures = schedulingResult.getFailures(); // if (!failures.isEmpty()) { // for (Map.Entry> entry: failures.entrySet()) { // System.out.println("****** failures for task " + entry.getKey().getId()); // } // } - } }; final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, 50L, resultCallback); // First, fill 4 VMs, each with 8 cores, with A using 15 cores, B using 6 cores, and C using 11 cores, with @@ -265,25 +265,22 @@ public void testMultiTierAllocation() throws Exception { TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); final BlockingQueue assignmentResults = new LinkedBlockingQueue<>(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - final Map resultMap = schedulingResult.getResultMap(); - if (!resultMap.isEmpty()) { - for (VMAssignmentResult r: resultMap.values()) { - for (TaskAssignmentResult t: r.getTasksAssigned()) { - assignmentResults.offer((QueuableTask)t.getRequest()); - //System.out.println("******* Assignment for task " + t.getTaskId()); - } + Action1 resultCallback = schedulingResult -> { + final Map resultMap = schedulingResult.getResultMap(); + if (!resultMap.isEmpty()) { + for (VMAssignmentResult r: resultMap.values()) { + for (TaskAssignmentResult t: r.getTasksAssigned()) { + assignmentResults.offer((QueuableTask)t.getRequest()); + //System.out.println("******* Assignment for task " + t.getTaskId()); } } + } // final Map> failures = schedulingResult.getFailures(); // if (!failures.isEmpty()) { // for (Map.Entry> entry: failures.entrySet()) { // System.out.println("****** failures for task " + entry.getKey().getId()); // } // } - } }; final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, 50L, resultCallback); // fill 4 hosts with tasks from A (tier 0) and tasks from D1 (tier 1) @@ -313,17 +310,14 @@ public void call(SchedulingResult schedulingResult) { Assert.assertEquals(tier1bktA.getBucketName(), task.getQAttributes().getBucketName()); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference>> ref = new AtomicReference<>(); - schedulingService.requestAllTasks(new Action1>>() { - @Override - public void call(Map> stateCollectionMap) { - //System.out.println("**************** Got tasks collection"); - final Collection tasks = stateCollectionMap.get(TaskQueue.TaskState.QUEUED); - //System.out.println("********* size=" + tasks.size()); + schedulingService.requestAllTasks(stateCollectionMap -> { + //System.out.println("**************** Got tasks collection"); + final Collection tasks = stateCollectionMap.get(TaskQueue.TaskState.QUEUED); + //System.out.println("********* size=" + tasks.size()); // if (!tasks.isEmpty()) // System.out.println("******** bucket: " + tasks.iterator().next().getQAttributes().getBucketName()); - ref.set(stateCollectionMap); - latch.countDown(); - } + ref.set(stateCollectionMap); + latch.countDown(); }); if (!latch.await(1000, TimeUnit.MILLISECONDS)) Assert.fail("Time out waiting for tasks collection"); @@ -341,25 +335,22 @@ public void testMultiResAllocation() throws Exception { final TaskScheduler scheduler = getScheduler(); final BlockingQueue assignmentResults = new LinkedBlockingQueue<>(); final AtomicReference ref = new AtomicReference<>(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - final Map resultMap = schedulingResult.getResultMap(); - if (!resultMap.isEmpty()) { - for (VMAssignmentResult r: resultMap.values()) { - for (TaskAssignmentResult t: r.getTasksAssigned()) { - assignmentResults.offer((QueuableTask)t.getRequest()); - } - ref.get().addLeases(Collections.singletonList(LeaseProvider.getConsumedLease(r))); + Action1 resultCallback = schedulingResult -> { + final Map resultMap = schedulingResult.getResultMap(); + if (!resultMap.isEmpty()) { + for (VMAssignmentResult r: resultMap.values()) { + for (TaskAssignmentResult t: r.getTasksAssigned()) { + assignmentResults.offer((QueuableTask)t.getRequest()); } + ref.get().addLeases(Collections.singletonList(LeaseProvider.getConsumedLease(r))); } + } // final Map> failures = schedulingResult.getFailures(); // if (!failures.isEmpty()) { // for (Map.Entry> entry: failures.entrySet()) { // System.out.println("****** failures for task " + entry.getKey().getId()); // } // } - } }; final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, 50L, resultCallback); ref.set(schedulingService); @@ -405,15 +396,12 @@ public void call(SchedulingResult schedulingResult) { } final AtomicReference bucketRef = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); - schedulingService.requestAllTasks(new Action1>>() { - @Override - public void call(Map> stateCollectionMap) { - final Collection tasks = stateCollectionMap.get(TaskQueue.TaskState.QUEUED); - if (tasks != null && !tasks.isEmpty()) { - for (QueuableTask t : tasks) - bucketRef.set(t.getQAttributes().getBucketName()); - latch.countDown(); - } + schedulingService.requestAllTasks(stateCollectionMap -> { + final Collection tasks = stateCollectionMap.get(TaskQueue.TaskState.QUEUED); + if (tasks != null && !tasks.isEmpty()) { + for (QueuableTask t : tasks) + bucketRef.set(t.getQAttributes().getBucketName()); + latch.countDown(); } }); if (!latch.await(2000, TimeUnit.MILLISECONDS)) @@ -427,14 +415,11 @@ public void testRemoveFromQueue() throws Exception { final CountDownLatch latch = new CountDownLatch(1); TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); - if (schedulingResult.getResultMap().size() > 0) { - //System.out.println("Assignment on host " + schedulingResult.getResultMap().values().iterator().next().getHostname()); - latch.countDown(); - } + Action1 resultCallback = schedulingResult -> { + //System.out.println("Got scheduling result with " + schedulingResult.getResultMap().size() + " results"); + if (schedulingResult.getResultMap().size() > 0) { + //System.out.println("Assignment on host " + schedulingResult.getResultMap().values().iterator().next().getHostname()); + latch.countDown(); } }; final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, 100L, 200L, resultCallback); @@ -447,15 +432,12 @@ public void call(SchedulingResult schedulingResult) { Assert.fail("Did not assign resources in time"); final CountDownLatch latch2 = new CountDownLatch(1); final AtomicBoolean found = new AtomicBoolean(); - schedulingService.requestVmCurrentStates(new Action1>() { - @Override - public void call(List states) { - for (VirtualMachineCurrentState s: states) { - for (TaskRequest t: s.getRunningTasks()) { - if (t.getId().equals(task.getId())) { - found.set(true); - latch2.countDown(); - } + schedulingService.requestVmCurrentStates(states -> { + for (VirtualMachineCurrentState s: states) { + for (TaskRequest t: s.getRunningTasks()) { + if (t.getId().equals(task.getId())) { + found.set(true); + latch2.countDown(); } } } @@ -467,19 +449,16 @@ public void call(List states) { schedulingService.removeTask(task.getId(), task.getQAttributes(), leases.get(0).hostname()); found.set(false); final CountDownLatch latch3 = new CountDownLatch(1); - schedulingService.requestVmCurrentStates(new Action1>() { - @Override - public void call(List states) { - for (VirtualMachineCurrentState s: states) { - for (TaskRequest t: s.getRunningTasks()) { - if (t.getId().equals(task.getId())) { - found.set(true); - latch3.countDown(); - } + schedulingService.requestVmCurrentStates(states -> { + for (VirtualMachineCurrentState s: states) { + for (TaskRequest t: s.getRunningTasks()) { + if (t.getId().equals(task.getId())) { + found.set(true); + latch3.countDown(); } } - latch3.countDown(); } + latch3.countDown(); }); if (!latch3.await(5, TimeUnit.SECONDS)) { Assert.fail("Timeout waiting for vm states"); @@ -493,11 +472,8 @@ public void testMaxSchedIterDelay() throws Exception { TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); queue.queueTask(QueuableTaskProvider.wrapTask(tier1bktA, TaskRequestProvider.getTaskRequest(1, 100, 1))); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - // no-op - } + Action1 resultCallback = schedulingResult -> { + // no-op }; final long maxDelay = 500L; final long loopMillis = 50L; @@ -536,11 +512,8 @@ public void call(SchedulingResult schedulingResult) { public void testInitWithPrevRunningTasks() throws Exception { TaskQueue queue = TaskQueues.createTieredQueue(2); final TaskScheduler scheduler = getScheduler(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - // no-op - } + Action1 resultCallback = schedulingResult -> { + // no-op }; final long maxDelay = 500L; final long loopMillis = 50L; @@ -554,15 +527,12 @@ public void call(SchedulingResult schedulingResult) { final AtomicReference ref = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); schedulingService.requestVmCurrentStates( - new Action1>() { - @Override - public void call(List states) { - if (states != null && !states.isEmpty()) { - final VirtualMachineCurrentState state = states.iterator().next(); - ref.set(state.getHostname()); - } - latch.countDown(); + states -> { + if (states != null && !states.isEmpty()) { + final VirtualMachineCurrentState state = states.iterator().next(); + ref.set(state.getHostname()); } + latch.countDown(); } ); if (!latch.await(maxDelay * 2, TimeUnit.MILLISECONDS)) { @@ -582,26 +552,23 @@ public void testLargeTasksToInitInRunningState() throws Exception { final CountDownLatch latch = new CountDownLatch(6); final AtomicReference> ref = new AtomicReference<>(); final AtomicBoolean printFailures = new AtomicBoolean(); - Action1 resultCallback = new Action1() { - @Override - public void call(SchedulingResult schedulingResult) { - final List exceptions = schedulingResult.getExceptions(); - if (exceptions != null && !exceptions.isEmpty()) - ref.set(exceptions); - else if (!schedulingResult.getResultMap().isEmpty()) - System.out.println("#Assignments: " + schedulingResult.getResultMap().values().iterator().next().getTasksAssigned().size()); - else if(printFailures.get()) { - final Map> failures = schedulingResult.getFailures(); - if (!failures.isEmpty()) { - for (Map.Entry> entry: failures.entrySet()) { - System.out.println(" Failure for " + entry.getKey().getId() + ":"); - for(TaskAssignmentResult r: entry.getValue()) - System.out.println(" " + r.toString()); - } + Action1 resultCallback = schedulingResult -> { + final List exceptions = schedulingResult.getExceptions(); + if (exceptions != null && !exceptions.isEmpty()) + ref.set(exceptions); + else if (!schedulingResult.getResultMap().isEmpty()) + System.out.println("#Assignments: " + schedulingResult.getResultMap().values().iterator().next().getTasksAssigned().size()); + else if(printFailures.get()) { + final Map> failures = schedulingResult.getFailures(); + if (!failures.isEmpty()) { + for (Map.Entry> entry: failures.entrySet()) { + System.out.println(" Failure for " + entry.getKey().getId() + ":"); + for(TaskAssignmentResult r: entry.getValue()) + System.out.println(" " + r.toString()); } } - latch.countDown(); } + latch.countDown(); }; final long maxDelay = 100L; final long loopMillis = 20L; @@ -681,13 +648,36 @@ else if (!resultMap.isEmpty()) { schedulingService.shutdown(); } - private void setupTaskGetter(TaskSchedulingService schedulingService, final AtomicLong gotTasksAt, final CountDownLatch latch) throws TaskQueueException { - schedulingService.requestAllTasks(new Action1>>() { - @Override - public void call(Map> stateCollectionMap) { - gotTasksAt.set(System.currentTimeMillis()); + @Test + public void testAssignableVMsEvaluator() throws Exception { + int numVms = 10; + int numTasks = 5; + long loopMillis = 100; + TaskQueue queue = TaskQueues.createTieredQueue(2); + final CountDownLatch latch = new CountDownLatch(1); + final TaskScheduler scheduler = getScheduler(avms -> avms.stream().limit(1).collect(Collectors.toList())); + final AtomicReference ref = new AtomicReference<>(); + Action1 resultCallback = schedulingResult -> { + if (schedulingResult.getTotalVMsCount() == numVms && schedulingResult.getFailures().size() == numTasks - 1) { latch.countDown(); } + }; + final TaskSchedulingService schedulingService = getSchedulingService(queue, scheduler, loopMillis, resultCallback); + ref.set(schedulingService); + schedulingService.start(); + schedulingService.addLeases(LeaseProvider.getLeases(numVms, 1, 4000, 1, 10)); + for (int i = 0; i < numTasks; i++) { + queue.queueTask(QueuableTaskProvider.wrapTask(tier1bktA, TaskRequestProvider.getTaskRequest(1, 1000, 1))); + } + if (!latch.await(loopMillis * 10, TimeUnit.MILLISECONDS)) { + Assert.fail("Latch timed out without having a successful scheduling result"); + } + } + + private void setupTaskGetter(TaskSchedulingService schedulingService, final AtomicLong gotTasksAt, final CountDownLatch latch) throws TaskQueueException { + schedulingService.requestAllTasks(stateCollectionMap -> { + gotTasksAt.set(System.currentTimeMillis()); + latch.countDown(); }); }