content: /**
   * 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 org.apache.aurora.scheduler.sla;
  
  import java.util.LinkedList;
  import java.util.Map;
  import java.util.Set;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.Range;
  import com.google.common.collect.Sets;
  
  import org.apache.aurora.gen.ScheduleStatus;
  import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
  import org.junit.Test;
  
  import static org.apache.aurora.gen.ScheduleStatus.ASSIGNED;
  import static org.apache.aurora.gen.ScheduleStatus.INIT;
  import static org.apache.aurora.gen.ScheduleStatus.KILLED;
  import static org.apache.aurora.gen.ScheduleStatus.KILLING;
  import static org.apache.aurora.gen.ScheduleStatus.LOST;
  import static org.apache.aurora.gen.ScheduleStatus.PARTITIONED;
  import static org.apache.aurora.gen.ScheduleStatus.PENDING;
  import static org.apache.aurora.gen.ScheduleStatus.RESTARTING;
  import static org.apache.aurora.gen.ScheduleStatus.RUNNING;
  import static org.apache.aurora.gen.ScheduleStatus.STARTING;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.AGGREGATE_PLATFORM_UPTIME;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.JOB_UPTIME_50;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.JOB_UPTIME_75;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.JOB_UPTIME_90;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.JOB_UPTIME_95;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.JOB_UPTIME_99;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.MEDIAN_TIME_TO_ASSIGNED;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.MEDIAN_TIME_TO_RUNNING;
  import static org.apache.aurora.scheduler.sla.SlaAlgorithm.AlgorithmType.MEDIAN_TIME_TO_STARTING;
  import static org.junit.Assert.assertEquals;
  
  public class SlaAlgorithmTest {
  
    @Test
    public void testMedianTimeToAssignedEven() {
      Number actual = MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING, 200L, ASSIGNED, 250L, KILLED)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, RUNNING)),
              makeTask(ImmutableMap.of(200L, PENDING, 250L, ASSIGNED, 350L, STARTING))),
          Range.closedOpen(0L, 300L));
      assertEquals(75.0, actual);
    }
  
    @Test
    public void testMedianTimeToAssignedOdd() {
      Number actual = MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING, 200L, ASSIGNED, 250L, RUNNING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, RUNNING)),
              makeTask(ImmutableMap.of(200L, PENDING, 250L, ASSIGNED, 350L, STARTING))),
          Range.closedOpen(0L, 300L));
      assertEquals(100.0, actual);
    }
  
    @Test
    public void testMedianTimeToAssignedZero() {
      Number actual = MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, KILLED))),
          Range.closedOpen(0L, 300L));
      assertEquals(0.0, actual);
    }
  
    @Test
    public void testMedianTimeToAssignedOne() {
      Number actual = MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED))),
          Range.closedOpen(0L, 300L));
      assertEquals(100.0, actual);
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void testMedianTimeToAssignedNoPending() {
      MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, ASSIGNED))),
          Range.closedOpen(0L, 300L));
    }
  
    @Test
    public void testMedianTimeToStartingEven() {
      Number actual = MEDIAN_TIME_TO_STARTING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)), // Ignored as not RUNNING
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 150L, STARTING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, STARTING, 400L, RUNNING)),
              makeTask(ImmutableMap.of(
                  100L, PENDING,
                  200L, ASSIGNED,
                  300L, STARTING,
                  400L, KILLED)), // Ignored due to being terminal.
              makeTask(ImmutableMap.of(
                  50L, PENDING,
                  100L, ASSIGNED,
                  150L, STARTING,
                  200L, RUNNING,
                  300L, KILLED))), // Ignored due to being terminal.
          Range.closedOpen(0L, 500L));
      assertEquals(150.0, actual);
    }
  
    @Test
    public void testMedianTimeToStartingOdd() {
      Number actual = MEDIAN_TIME_TO_STARTING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)), // Ignored as not RUNNING
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 150L, STARTING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, STARTING, 400L, RUNNING)),
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 350L, STARTING)),
              makeTask(ImmutableMap.of(
                  100L, PENDING,
                  200L, ASSIGNED,
                  300L, STARTING,
                  400L, KILLED)), // Ignored due to being terminal.
              makeTask(ImmutableMap.of(
                  50L, PENDING,
                  100L, ASSIGNED,
                  150L, STARTING,
                  200L, RUNNING,
                  300L, KILLED))), // Ignored due to being terminal.
          Range.closedOpen(0L, 500L));
      assertEquals(200.0, actual);
    }
  
    @Test
    public void testMedianTimeToStartingZero() {
      Number actual = MEDIAN_TIME_TO_STARTING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)),
              makeTask(ImmutableMap.of(50L, PENDING, 100L, STARTING, 200L, RUNNING, 300L, KILLED)),
              makeTask(ImmutableMap.of(50L, PENDING, 100L, STARTING, 200L, KILLED))),
          Range.closedOpen(0L, 500L));
      assertEquals(0.0, actual);
    }
  
    @Test
    public void testMedianTimeToRunningEven() {
      Number actual = MEDIAN_TIME_TO_RUNNING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)), // Ignored as not RUNNING
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 150L, STARTING, 180L, RUNNING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, STARTING, 400L, RUNNING)),
              makeTask(ImmutableMap.of(
                  50L, PENDING,
                  100L, ASSIGNED,
                  150L, STARTING,
                  200L, RUNNING,
                  300L, KILLED))), // Ignored due to being terminal.
          Range.closedOpen(0L, 500L));
      assertEquals(215.0, actual);
    }
  
    @Test
    public void testMedianTimeToRunningOdd() {
      Number actual = MEDIAN_TIME_TO_RUNNING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)), // Ignored as not RUNNING
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 150L, STARTING, 180L, RUNNING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED, 300L, STARTING, 400L, RUNNING)),
              makeTask(ImmutableMap.of(50L, PENDING, 100L, ASSIGNED, 150L, STARTING, 200L, RUNNING))),
          Range.closedOpen(0L, 500L));
      assertEquals(150.0, actual);
    }
  
    @Test
    public void testMedianTimeToRunningZero() {
      Number actual = MEDIAN_TIME_TO_RUNNING.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)),
              makeTask(ImmutableMap.of(50L, PENDING, 100L, RUNNING, 200L, KILLED))),
          Range.closedOpen(0L, 500L));
      assertEquals(0.0, actual);
    }
  
    @Test
    public void testMedianTimeEventsOutsideTimeRangeIgnored() {
      Number actual = MEDIAN_TIME_TO_ASSIGNED.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, PENDING)),
              makeTask(ImmutableMap.of(100L, PENDING, 200L, ASSIGNED)),
              makeTask(ImmutableMap.of(100L, PENDING, 300L, ASSIGNED)),
              makeTask(ImmutableMap.of(100L, PENDING, 150L, ASSIGNED)),
              makeTask(ImmutableMap.of(100L, PENDING, 260L, ASSIGNED)),
              makeTask(ImmutableMap.of(100L, PENDING, 400L, ASSIGNED))),
          Range.closedOpen(200L, 300L));
      assertEquals(130.0, actual);
    }
  
    @Test
    public void testJobUptime50() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_50.getAlgorithm().calculate(
          makeUptimeTasks(100, now),
          Range.closed(0L, now));
      assertEquals(50.5, actual);
    }
  
    @Test
    public void testJobUptime75() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_75.getAlgorithm().calculate(
          makeUptimeTasks(100, now),
          Range.closed(0L, now));
      assertEquals(25.75, actual);
    }
  
    @Test
    public void testJobUptime90() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_90.getAlgorithm().calculate(
          makeUptimeTasks(100, now),
          Range.closed(0L, now));
      assertEquals(10.9, actual);
    }
  
    @Test
    public void testJobUptime95() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_95.getAlgorithm().calculate(
          makeUptimeTasks(100, now),
          Range.closed(0L, now));
      assertEquals(5.95, actual);
    }
  
    @Test
    public void testJobUptime99() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_99.getAlgorithm().calculate(
          makeUptimeTasks(100, now),
          Range.closed(0L, now));
      assertEquals(1.99, actual);
    }
  
    @Test
    public void testJobUptimeEmpty() {
      long now = System.currentTimeMillis();
      Number actual = JOB_UPTIME_99.getAlgorithm().calculate(
          new LinkedList<IScheduledTask>(),
          Range.closed(0L, now));
      assertEquals(0.0, actual);
    }
  
    @Test
    public void testJobUptimeNonTerminalIgnored() {
      long now = System.currentTimeMillis();
      Set<IScheduledTask> instances = makeUptimeTasks(100, now);
      instances.add(makeTask(ImmutableMap.of(now - 5000, RUNNING, now - 3000, KILLED)));
      Number actual = JOB_UPTIME_99.getAlgorithm().calculate(instances, Range.closed(0L, now));
      assertEquals(1.99, actual);
    }
  
    @Test
    public void testJobUptimeLiveNonTerminalIgnored() {
      long now = System.currentTimeMillis();
      Set<IScheduledTask> instances = makeUptimeTasks(100, now);
      instances.add(makeTask(ImmutableMap.of(now - 5000, RUNNING, now - 3000, RESTARTING)));
      Number actual = JOB_UPTIME_99.getAlgorithm().calculate(instances, Range.closed(0L, now));
      assertEquals(1.99, actual);
    }
  
    @Test
    public void testAggregatePlatformUptime() {
      Number actual = AGGREGATE_PLATFORM_UPTIME.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(100L, PENDING), 0), // Ignored.
              makeTask(ImmutableMap.of(
                  100L, PENDING,
                  200L, ASSIGNED,
                  300L, STARTING,
                  400L, RUNNING,
                  500L, PARTITIONED), 1), // 100% uptime.
              makeTask(ImmutableMap.<Long, ScheduleStatus>builder()
                  .put(5L, INIT)
                  .put(10L, PENDING)
                  .put(20L, ASSIGNED)
                  .put(30L, STARTING)
                  .put(50L, RUNNING)
                  .put(400L, KILLING)
                  .put(450L, KILLED).build(), 2)), // 100% uptime.
          Range.closedOpen(100L, 500L));
      assertEquals(100.0, actual);
    }
  
    @Test
    public void testAggregatePlatformUptimeRecoveredFromDown() {
      Number actual = AGGREGATE_PLATFORM_UPTIME.getAlgorithm().calculate(
          ImmutableSet.of(
              makeTask(ImmutableMap.of(50L, RUNNING, 300L, LOST, 310L, KILLED), 0), // DOWN mid range.
              makeTask(ImmutableMap.of(
                  320L, PENDING,
                  330L, ASSIGNED,
                  350L, STARTING,
                  400L, RUNNING), 0)),  // Recovered within range.
          Range.closedOpen(100L, 500L));
      assertEquals(75.0, actual);
    }
  
    @Test
    public void testAggregatePlatformUptimeKilledByPlatform() {
      Number actual = AGGREGATE_PLATFORM_UPTIME.getAlgorithm().calculate(
          ImmutableSet.of(makeTask(ImmutableMap.of(50L, RUNNING, 300L, KILLED), 0)),
          Range.closedOpen(100L, 500L));
      assertEquals(50.0, actual);
    }
  
    @Test
    public void testAggregatePlatformUptimeEmpty() {
      Number actual = AGGREGATE_PLATFORM_UPTIME.getAlgorithm().calculate(
          ImmutableSet.of(makeTask(ImmutableMap.of(50L, PENDING), 0)),
          Range.closedOpen(100L, 500L));
      assertEquals(100.0, actual);
    }
  
    @Test
    public void testSupportAllStatuses() {
      for (ScheduleStatus status: ScheduleStatus.values()) {
        AGGREGATE_PLATFORM_UPTIME.getAlgorithm().calculate(
            ImmutableSet.of(makeTask(ImmutableMap.of(50L, status), 0)),
            Range.closedOpen(100L, 500L));
      }
    }
  
    private static Set<IScheduledTask> makeUptimeTasks(int num, long now) {
      Set<IScheduledTask> instances = Sets.newHashSet();
      for (int i = 0; i < num; i++) {
        instances.add(makeTask(ImmutableMap.of(now - (i + 1) * 1000, RUNNING)));
      }
      return instances;
    }
  
    private static IScheduledTask makeTask(Map<Long, ScheduleStatus> events) {
      return makeTask(events, 0);
    }
  
    private static IScheduledTask makeTask(Map<Long, ScheduleStatus> events, int instanceId) {
      return SlaTestUtil.makeTask(events, instanceId);
    }
  }
  