/*-
* -\-\-
* Helios Services
* --
* Copyright (C) 2016 Spotify AB
* --
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -/-/-
*/
package com.spotify.helios.master.reaper;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.Lists;
import com.spotify.helios.common.Clock;
import com.spotify.helios.common.descriptors.AgentInfo;
import com.spotify.helios.common.descriptors.HostStatus;
import com.spotify.helios.master.MasterModel;
import java.util.List;
import java.util.stream.Collectors;
import org.joda.time.Instant;
import org.junit.Test;
public class DeadAgentReaperTest {
private static final long TIMEOUT_HOURS = 1000;
private static class Datapoint {
private final String host;
private final long startTime;
private final long uptime;
private final HostStatus.Status status;
private final boolean expectReap;
private Datapoint(final String host, final long startTimeHours, final long uptimeHours,
final HostStatus.Status status, final boolean expectReap) {
this.host = host;
this.startTime = HOURS.toMillis(startTimeHours);
this.uptime = HOURS.toMillis(uptimeHours);
this.status = status;
this.expectReap = expectReap;
}
}
@Test
public void testDeadAgentReaper() throws Exception {
final MasterModel masterModel = mock(MasterModel.class);
final Clock clock = mock(Clock.class);
when(clock.now()).thenReturn(new Instant(HOURS.toMillis(2000)));
final List<Datapoint> datapoints = Lists.newArrayList(
new Datapoint("host1", 0, TIMEOUT_HOURS - 1, HostStatus.Status.DOWN, true),
new Datapoint("host2", 0, TIMEOUT_HOURS + 1, HostStatus.Status.DOWN, false),
new Datapoint("host3", 1000, 1000, HostStatus.Status.UP, false),
new Datapoint("host4", 500, 300, HostStatus.Status.DOWN, true),
// Agents started in the future should not be reaped, even if they are reported as down
new Datapoint("host5", 5000, 0, HostStatus.Status.DOWN, false),
// Agents that are UP should not be reaped even if the start and uptime indicate that
// they should
new Datapoint("host6", 0, 0, HostStatus.Status.UP, false)
);
when(masterModel.listHosts()).thenReturn(Lists.newArrayList(
datapoints.stream().map(input -> input.host).collect(Collectors.toList())));
for (final Datapoint datapoint : datapoints) {
when(masterModel.isHostUp(datapoint.host))
.thenReturn(HostStatus.Status.UP == datapoint.status);
when(masterModel.getAgentInfo(datapoint.host)).thenReturn(AgentInfo.newBuilder()
.setStartTime(datapoint.startTime)
.setUptime(datapoint.uptime)
.build());
}
final DeadAgentReaper reaper = new DeadAgentReaper(masterModel, TIMEOUT_HOURS, clock, 100, 0);
reaper.startAsync().awaitRunning();
for (final Datapoint datapoint : datapoints) {
if (datapoint.expectReap) {
verify(masterModel, timeout(500)).deregisterHost(datapoint.host);
} else {
verify(masterModel, never()).deregisterHost(datapoint.host);
}
}
}
}