package io.vivarium.server.workloadmanagement; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.Test; import org.junit.experimental.categories.Category; import com.google.common.collect.Lists; import com.johnuckele.vtest.Tester; import io.vivarium.persistence.WorkerModel; import io.vivarium.test.FastTest; import io.vivarium.test.UnitTest; public class JobAssingmentsTest { @Test @Category({ FastTest.class, UnitTest.class }) public void testScoreCalculationsForSingleWorker() { // Mock a worker WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); // Build the job assignments object JobAssignments jobAssignments = new JobAssignments(Lists.newArrayList(worker1)); long score; long scoreImprovement; // Test scores & add jobs // Should start with a score of zero score = jobAssignments.getScore(); Tester.equal("Initial score should be zero", score, 0); // Adding a priority 3 job should increase the score 45 (with a new value of 45). // 45 = 15 (the first slot) * 3 (the priority) scoreImprovement = jobAssignments.getScoreChangeForJob(worker1, 3); Tester.equal("Adding a priority 3 job should improve the score by 45", scoreImprovement, 45); jobAssignments.addWorkerJob(worker1, 3); score = jobAssignments.getScore(); Tester.equal("Total score should now be 45", score, 45); // Explore adding a 1 priority job. This would give a total score of around 50, but due to rounding will // actually be 48, (25/2 = 12, 12 * 3 + 12 * 1 = 48). scoreImprovement = jobAssignments.getScoreChangeForJob(worker1, 1); Tester.equal("Adding a priority 1 job should improve the score by 3", scoreImprovement, 3); // Seems uncompelling to add a priority one job, but maybe a priort 3 job just came in... // 25 / 2 * 2 * 3 = 72, so we would count a priority 3 job as a +27 improvement to score scoreImprovement = jobAssignments.getScoreChangeForJob(worker1, 3); Tester.equal("Adding a priority 3 job should improve the score by 27", scoreImprovement, 27); jobAssignments.addWorkerJob(worker1, 3); score = jobAssignments.getScore(); Tester.equal("Total score should now be 72", score, 72); // What if we thought about adding that priority one job now? // 30 / 3 = 10, 10 * 2 * 3 = 60, 10 * 1 * 1 = 10. 60 + 10 = 70. Actually decreases score! scoreImprovement = jobAssignments.getScoreChangeForJob(worker1, 1); Tester.equal("Adding a priority 1 job should decrease the score by 2", scoreImprovement, -2); jobAssignments.addWorkerJob(worker1, 1); score = jobAssignments.getScore(); Tester.equal("Total score should now be 70", score, 70); } @Test @Category({ FastTest.class, UnitTest.class }) public void testScoreCalculationsForMultiWorker() { // Mock a few workers WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker2 = mock(WorkerModel.class); when(worker2.getThroughputs()).thenReturn(new long[] { 50, 60, 70 }); WorkerModel worker3 = mock(WorkerModel.class); when(worker3.getThroughputs()).thenReturn(new long[] { 20, 22, 23, 24, 25 }); // Build the job assignments object JobAssignments jobAssignments = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); long score; long scoreImprovement; // Test scores & add jobs // Should start with a score of zero score = jobAssignments.getScore(); Tester.equal("Initial score should be zero", score, 0); // Add priority 1 jobs to each worker scoreImprovement = jobAssignments.getScoreChangeForJob(worker1, 1); Tester.equal("Adding a priority 1 job to worker 1 should improve the score by 15", scoreImprovement, 15); scoreImprovement = jobAssignments.getScoreChangeForJob(worker2, 1); Tester.equal("Adding a priority 1 job to worker 2 should improve the score by 15", scoreImprovement, 50); scoreImprovement = jobAssignments.getScoreChangeForJob(worker3, 1); Tester.equal("Adding a priority 1 job to worker 3 should improve the score by 15", scoreImprovement, 20); // Adding a job to a worker doesn't matter which order they occur in, so we can test every worker and then add // and see the same improvements. jobAssignments.addWorkerJob(worker1, 1); score = jobAssignments.getScore(); Tester.equal("Total score should now be 15", score, 15); jobAssignments.addWorkerJob(worker2, 1); score = jobAssignments.getScore(); Tester.equal("Total score should now be 15", score, 65); jobAssignments.addWorkerJob(worker3, 1); score = jobAssignments.getScore(); Tester.equal("Total score should now be 15", score, 85); // Show that a late high priority addition can significantly improve score jobAssignments.addWorkerJob(worker3, 1); jobAssignments.addWorkerJob(worker3, 1); jobAssignments.addWorkerJob(worker3, 1); // Total score for worker 3 should now be 24, so total for the group should be 89 score = jobAssignments.getScore(); Tester.equal("Total score should now be 89", score, 89); // Adding one more priority 1 job to worker 3 would be a score improvement of 1 scoreImprovement = jobAssignments.getScoreChangeForJob(worker3, 1); Tester.equal("Adding a final priority 1 job to worker 3 should improve the score by 1", scoreImprovement, 1); // However, adding a priority 10 job (25 / 5 = 5, 5 * 4 * 1 = 20, 5 * 1 * 10 = 10, 20 + 10 = 30), should improve // the score by 6. scoreImprovement = jobAssignments.getScoreChangeForJob(worker3, 2); Tester.equal("Adding a priority 10 job to worker 3 should improve the score by 6", scoreImprovement, 6); } @Test(expected = IllegalArgumentException.class) @Category({ FastTest.class, UnitTest.class }) public void testAddingJobsToNonExistantWorker() { // Mock a couple of workers WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker2 = mock(WorkerModel.class); when(worker2.getThroughputs()).thenReturn(new long[] { 50, 60, 70 }); // Build the job assignments object JobAssignments jobAssignments = new JobAssignments(Lists.newArrayList(worker1)); // Add a Worker job with a worker we did not give to the JobAssignments object jobAssignments.addWorkerJob(worker2, 1); Tester.fail("The above code should have failed."); } @Test(expected = ArrayIndexOutOfBoundsException.class) @Category({ FastTest.class, UnitTest.class }) public void testOverloadingWorkerWithJobs() { // Mock a worker WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); // Build the job assignments object JobAssignments jobAssignments = new JobAssignments(Lists.newArrayList(worker1)); // Add a too many jobs for (int i = 0; i < 100; i++) { jobAssignments.addWorkerJob(worker1, 3); } Tester.fail("The above code should have failed."); } @Test @Category({ FastTest.class, UnitTest.class }) public void testSubtractForEmptyJobAssignments() { // Mock a worker WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker2 = mock(WorkerModel.class); when(worker2.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker3 = mock(WorkerModel.class); when(worker3.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); // Build the job assignments objects JobAssignments jobAssignments1 = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); JobAssignments jobAssignments2 = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); JobAssignments difference = JobAssignments.subtract(jobAssignments1, jobAssignments2); Tester.isNotNull("Subtracted job assignments should exist", difference); long score = difference.getScore(); Tester.equal("Score should be zero", score, 0); } @Test @Category({ FastTest.class, UnitTest.class }) public void testSubtractForJobAssignments() { // Mock a worker WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker2 = mock(WorkerModel.class); when(worker2.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker3 = mock(WorkerModel.class); when(worker3.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); // Build the job assignments objects JobAssignments oldAssignments = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); JobAssignments newAssignments = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); // Assign jobs on the first assignment object oldAssignments.addWorkerJob(worker1, 1); oldAssignments.addWorkerJob(worker1, 1); oldAssignments.addWorkerJob(worker1, 3); oldAssignments.addWorkerJob(worker2, 2); oldAssignments.addWorkerJob(worker2, 2); // Assign jobs on the second assignment object newAssignments.addWorkerJob(worker1, 3); newAssignments.addWorkerJob(worker1, 3); newAssignments.addWorkerJob(worker3, 1); newAssignments.addWorkerJob(worker3, 2); newAssignments.addWorkerJob(worker3, 2); // Compute both differences JobAssignments removedDifference = JobAssignments.subtract(oldAssignments, newAssignments); JobAssignments addedDifference = JobAssignments.subtract(newAssignments, oldAssignments); // Validate that all the differences are correct int worker1Priority1JobsRemoved = removedDifference.getJobPriorityCounts(worker1).get(1); int worker1Priority1JobsAdded = addedDifference.getJobPriorityCounts(worker1).get(1); Tester.equal("Worker 1 should have 2 removed priority 1 jobs", worker1Priority1JobsRemoved, 2); Tester.equal("Worker 1 should have 0 added priority 1 jobs", worker1Priority1JobsAdded, 0); int worker1Priority3JobsRemoved = removedDifference.getJobPriorityCounts(worker1).get(3); int worker1Priority3JobsAdded = addedDifference.getJobPriorityCounts(worker1).get(3); Tester.equal("Worker 1 should have 0 removed priority 3 jobs", worker1Priority3JobsRemoved, 0); Tester.equal("Worker 1 should have 1 added priority 3 jobs", worker1Priority3JobsAdded, 1); int worker2Priority2JobsRemoved = removedDifference.getJobPriorityCounts(worker2).get(2); int worker2Priority2JobsAdded = addedDifference.getJobPriorityCounts(worker2).get(2); Tester.equal("Worker 2 should have 2 removed priority 2 jobs", worker2Priority2JobsRemoved, 2); Tester.equal("Worker 2 should have 0 added priority 2 jobs", worker2Priority2JobsAdded, 0); int worker3Priority2JobsRemoved = removedDifference.getJobPriorityCounts(worker3).get(2); int worker3Priority2JobsAdded = addedDifference.getJobPriorityCounts(worker3).get(2); Tester.equal("Worker 3 should have 0 removed priority 2 jobs", worker3Priority2JobsRemoved, 0); Tester.equal("Worker 3 should have 2 added priority 2 jobs", worker3Priority2JobsAdded, 2); } @Test @Category({ FastTest.class, UnitTest.class }) public void testSubtractWorkerSetValidation() { // Mock a worker WorkerModel worker1 = mock(WorkerModel.class); when(worker1.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker2 = mock(WorkerModel.class); when(worker2.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); WorkerModel worker3 = mock(WorkerModel.class); when(worker3.getThroughputs()).thenReturn(new long[] { 15, 25, 30, 34, 37, 39, 40 }); try { // Build the job assignments objects JobAssignments jobAssignments1 = new JobAssignments(Lists.newArrayList(worker1, worker2)); JobAssignments jobAssignments2 = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); // This will fail because the JobAssignments objects don't share the same workers JobAssignments.subtract(jobAssignments1, jobAssignments2); Tester.fail("The above code should have failed."); } catch (IllegalArgumentException e) { Tester.pass( "If the minued has an incomplete subset of workers that the subtrahend has, subtract throws an IllegalArgumentException"); } try { // Build the job assignments objects JobAssignments jobAssignments1 = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); JobAssignments jobAssignments2 = new JobAssignments(Lists.newArrayList(worker2, worker3)); // This will fail because the JobAssignments objects don't share the same workers JobAssignments.subtract(jobAssignments1, jobAssignments2); Tester.fail("The above code should have failed."); } catch (IllegalArgumentException e) { Tester.pass( "If the subtrahend has an incomplete subset of workers that the minued has, subtract throws an IllegalArgumentException"); } try { // Build the job assignments objects JobAssignments jobAssignments1 = new JobAssignments(Lists.newArrayList(worker3, worker1, worker2)); JobAssignments jobAssignments2 = new JobAssignments(Lists.newArrayList(worker1, worker2, worker3)); // This will fail because the JobAssignments objects don't share the same workers JobAssignments.subtract(jobAssignments1, jobAssignments2); Tester.pass( "If the subtrahend and minued have the same set, the code will not throw regardless of ordering"); } catch (IllegalArgumentException e) { Tester.fail("The above code should not have failed."); } try { // Build the job assignments objects JobAssignments jobAssignments1 = new JobAssignments(Lists.newArrayList(worker3, worker2)); JobAssignments jobAssignments2 = new JobAssignments(Lists.newArrayList(worker1, worker3)); // This will fail because the JobAssignments objects don't share the same workers JobAssignments.subtract(jobAssignments1, jobAssignments2); Tester.fail("The above code should have failed."); } catch (IllegalArgumentException e) { Tester.pass( "If the minued and subtrahend have worker sets of the same size but with different membership, subtract throws an IllegalArgumentException"); } } }