summaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-09-01 17:03:12 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-09-01 17:03:12 +0200
commit3549bbf2c64f047c4089ed7d430c7aa4ccf78a59 (patch)
tree997c1057ada0952d926b39018dc85cf5c75b20f1 /node-repository
parent9ea8c24df30cf239160b94be681c0d120c06bb5c (diff)
Track config requests and fail ready hosts which don't make them
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java53
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java13
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java102
3 files changed, 151 insertions, 17 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
index 9a116478882..e216a5c9354 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.Deployment;
+import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ServiceCluster;
@@ -20,6 +21,7 @@ import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
@@ -36,17 +38,24 @@ public class NodeFailer extends Maintainer {
private static final Logger log = Logger.getLogger(NodeFailer.class.getName());
- private final Deployer deployer;
+ /** Provides information about the status of ready hosts */
+ private final HostLivenessTracker hostLivenessTracker;
+
+ /** Provides (more accurate) information about the status of active hosts */
private final ServiceMonitor serviceMonitor;
+
+ private final Deployer deployer;
private final Duration downTimeLimit;
private final Clock clock;
private final Orchestrator orchestrator;
- public NodeFailer(Deployer deployer, ServiceMonitor serviceMonitor, NodeRepository nodeRepository,
+ public NodeFailer(Deployer deployer, HostLivenessTracker hostLivenessTracker,
+ ServiceMonitor serviceMonitor, NodeRepository nodeRepository,
Duration downTimeLimit, Clock clock, Orchestrator orchestrator) {
// check ping status every five minutes, but at least twice as often as the down time limit
super(nodeRepository, min(downTimeLimit.dividedBy(2), Duration.ofMinutes(5)));
this.deployer = deployer;
+ this.hostLivenessTracker = hostLivenessTracker;
this.serviceMonitor = serviceMonitor;
this.downTimeLimit = downTimeLimit;
this.clock = clock;
@@ -59,27 +68,57 @@ public class NodeFailer extends Maintainer {
@Override
protected void maintain() {
+ // Ready nodes
+ for (Node node : readyNodesWithHardwareFailure())
+ nodeRepository().fail(node.hostname());
+ for (Node node : readyNodesWhichAreDead())
+ nodeRepository().fail(node.hostname());
+
+ // Active nodes
for (Node node : determineActiveNodeDownStatus()) {
Instant graceTimeEnd = node.history().event(History.Event.Type.down).get().at().plus(downTimeLimit);
if (graceTimeEnd.isBefore(clock.instant()) && ! applicationSuspended(node))
failActive(node);
}
- for (Node node : readyNodesWithHardwareFailure()) {
- nodeRepository().fail(node.hostname());
- }
}
private List<Node> readyNodesWithHardwareFailure() {
return nodeRepository().getNodes(Node.Type.tenant, Node.State.ready).stream()
- .filter(n -> n.status().hardwareFailure())
+ .filter(node -> node.status().hardwareFailure())
.collect(Collectors.toList());
}
+ private List<Node> readyNodesWhichAreDead() {
+ // Nodes are taken as dead if they have not made a config request since this instant.
+ // Add 10 minutes to the down time limit to allow nodes to make a request that infrequently.
+ Instant oldestAcceptableRequestTime = clock.instant().minus(downTimeLimit).minus(Duration.ofMinutes(10));
+
+ if ( ! hostLivenessTracker.remembersRequestsSince().isBefore(oldestAcceptableRequestTime))
+ return Collections.emptyList(); // we haven't tracked long enough to tell if nodes are dead
+
+ return nodeRepository().getNodes(Node.Type.tenant, Node.State.ready).stream()
+ .filter(node -> wasMadeReadyBefore(oldestAcceptableRequestTime, node))
+ .filter(node -> ! hasRecordedResponseAfter(oldestAcceptableRequestTime, node))
+ .collect(Collectors.toList());
+ }
+
+ private boolean wasMadeReadyBefore(Instant instant, Node node) {
+ Optional<History.Event> readiedEvent = node.history().event(History.Event.Type.readied);
+ if ( ! readiedEvent.isPresent()) return false;
+ return readiedEvent.get().at().isBefore(instant);
+ }
+
+ private boolean hasRecordedResponseAfter(Instant instant, Node node) {
+ Optional<Instant> lastResponse = hostLivenessTracker.lastRequestFrom(node.hostname());
+ if ( ! lastResponse.isPresent()) return false;
+ return lastResponse.get().isAfter(instant);
+ }
+
private boolean applicationSuspended(Node node) {
try {
return orchestrator.getApplicationInstanceStatus(node.allocation().get().owner())
- == ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN;
+ == ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN;
} catch (ApplicationIdNotFoundException e) {
//Treat it as not suspended and allow to fail the node anyway
return false;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index c23a0fa5f56..2bc826adc30 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -5,6 +5,7 @@ import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.orchestrator.Orchestrator;
@@ -30,13 +31,17 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final DirtyExpirer dirtyExpirer;
@Inject
- public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, ServiceMonitor serviceMonitor, Zone zone, Orchestrator orchestrator) {
- this(nodeRepository, deployer, serviceMonitor, zone, Clock.systemUTC(), orchestrator);
+ public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer,
+ HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor,
+ Zone zone, Orchestrator orchestrator) {
+ this(nodeRepository, deployer, hostLivenessTracker, serviceMonitor, zone, Clock.systemUTC(), orchestrator);
}
- public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, ServiceMonitor serviceMonitor, Zone zone, Clock clock, Orchestrator orchestrator) {
+ public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer,
+ HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor,
+ Zone zone, Clock clock, Orchestrator orchestrator) {
DefaultTimes defaults = new DefaultTimes(zone.environment());
- nodeFailer = new NodeFailer(deployer, serviceMonitor, nodeRepository, fromEnv("fail_grace").orElse(defaults.failGrace), clock, orchestrator);
+ nodeFailer = new NodeFailer(deployer, hostLivenessTracker, serviceMonitor, nodeRepository, fromEnv("fail_grace").orElse(defaults.failGrace), clock, orchestrator);
applicationMaintainer = new ApplicationMaintainer(deployer, nodeRepository, fromEnv("redeploy_frequency").orElse(defaults.redeployFrequency));
reservationExpirer = new ReservationExpirer(nodeRepository, clock, fromEnv("reservation_expiry").orElse(defaults.reservationExpiry));
retiredExpirer = new RetiredExpirer(nodeRepository, deployer, clock, fromEnv("retired_expiry").orElse(defaults.retiredExpiry));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
index d12eb6ffe0b..e86316d11f0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
@@ -1,11 +1,13 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.google.inject.Inject;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.HostLivenessTracker;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
@@ -47,8 +49,11 @@ import com.yahoo.vespa.service.monitor.ServiceMonitorStatus;
import org.junit.Before;
import org.junit.Test;
+import java.time.Clock;
import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -76,6 +81,7 @@ public class NodeFailerTest {
// Components with state
private ManualClock clock;
private Curator curator;
+ private TestHostLivenessTracker hostLivenessTracker;
private ServiceMonitorStub serviceMonitor;
private MockDeployer deployer;
private NodeRepository nodeRepository;
@@ -107,13 +113,12 @@ public class NodeFailerTest {
apps.put(APP_1, new MockDeployer.ApplicationContext(APP_1, clusterApp1, wantedNodesApp1, Optional.of("default"), 1));
apps.put(APP_2, new MockDeployer.ApplicationContext(APP_2, clusterApp2, wantedNodesApp2, Optional.of("default"), 1));
deployer = new MockDeployer(provisioner, apps);
- // ... and a service monitor
+ // ... and the other services
+ hostLivenessTracker = new TestHostLivenessTracker(clock);
serviceMonitor = new ServiceMonitorStub(apps, nodeRepository);
-
orchestrator = new OrchestratorMock();
- failer = new NodeFailer(deployer, serviceMonitor, nodeRepository,
- DOWNTIME_LIMIT_ONE_HOUR, clock, orchestrator);
+ failer = new NodeFailer(deployer, hostLivenessTracker, serviceMonitor, nodeRepository, DOWNTIME_LIMIT_ONE_HOUR, clock, orchestrator);
}
@Test
@@ -139,6 +144,8 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 24 * 60; minutes +=5 ) {
failer.run();
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
+
assertEquals( 0, deployer.redeployments);
assertEquals(12, nodeRepository.getNodes(Node.Type.tenant, Node.State.active).size());
assertEquals( 0, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
@@ -164,6 +171,7 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 45; minutes +=5 ) {
failer.run();
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
assertEquals( 0, deployer.redeployments);
assertEquals(12, nodeRepository.getNodes(Node.Type.tenant, Node.State.active).size());
assertEquals( 2, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
@@ -173,6 +181,7 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 30; minutes +=5 ) {
failer.run();
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
}
// downHost2 should now be failed and replaced, but not downHost1
@@ -186,8 +195,10 @@ public class NodeFailerTest {
serviceMonitor.setHostDown(downHost1);
failer.run();
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
// the system goes down and do not have updated information when coming back
clock.advance(Duration.ofMinutes(120));
+ hostLivenessTracker.setConstructedNow();
serviceMonitor.setStatusIsKnown(false);
failer.run();
// due to this, nothing is failed
@@ -197,6 +208,7 @@ public class NodeFailerTest {
assertEquals( 1, nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).size());
// when status becomes known, and the host is still down, it is failed
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
serviceMonitor.setStatusIsKnown(true);
failer.run();
assertEquals( 2, deployer.redeployments);
@@ -211,6 +223,7 @@ public class NodeFailerTest {
for (int minutes = 0; minutes < 75; minutes +=5 ) {
failer.run();
clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
assertEquals( 2, deployer.redeployments);
assertEquals(12, nodeRepository.getNodes(Node.Type.tenant, Node.State.active).size());
assertEquals( 4, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
@@ -230,7 +243,51 @@ public class NodeFailerTest {
>
lastNode.allocation().get().membership().index());
}
+
+ @Test
+ public void testFailingReadyNodes() {
+ // For a day all nodes work so nothing happens
+ for (int minutes = 0; minutes < 24 * 60; minutes +=5 ) {
+ clock.advance(Duration.ofMinutes(5));
+ allNodesMakeAConfigRequestExcept();
+ failer.run();
+ assertEquals( 4, nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).size());
+ }
+
+ List<Node> ready = nodeRepository.getNodes(Node.Type.tenant, Node.State.ready);
+ // Two ready nodes die
+ clock.advance(Duration.ofMinutes(180));
+ allNodesMakeAConfigRequestExcept(ready.get(0), ready.get(2));
+ failer.run();
+ assertEquals( 2, nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).size());
+ assertEquals( 2, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
+
+ // Another ready node die but we restart so we don't have enough information
+ clock.advance(Duration.ofMinutes(180));
+ hostLivenessTracker.setConstructedNow();
+ allNodesMakeAConfigRequestExcept(ready.get(0), ready.get(2), ready.get(3));
+ failer.run();
+ assertEquals( 2, nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).size());
+ assertEquals( 2, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
+
+ // Now we get enough information
+ clock.advance(Duration.ofMinutes(180));
+ allNodesMakeAConfigRequestExcept(ready.get(0), ready.get(2), ready.get(3));
+ failer.run();
+ assertEquals( 1, nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).size());
+ assertEquals(ready.get(1), nodeRepository.getNodes(Node.Type.tenant, Node.State.ready).get(0));
+ assertEquals( 3, nodeRepository.getNodes(Node.Type.tenant, Node.State.failed).size());
+ }
+
+ private void allNodesMakeAConfigRequestExcept(Node ... deadNodeArray) {
+ Set<Node> deadNodes = new HashSet<>(Arrays.asList(deadNodeArray));
+ for (Node node : nodeRepository.getNodes(Node.Type.tenant)) {
+ if ( ! deadNodes.contains(node))
+ hostLivenessTracker.receivedRequestFrom(node.hostname());
+ }
+ }
+
private void createReadyNodes(int count, NodeRepository nodeRepository, NodeFlavors nodeFlavors) {
createReadyNodes(count, 0, nodeRepository, nodeFlavors);
}
@@ -269,6 +326,38 @@ public class NodeFailerTest {
return highestIndex;
}
+ /** This is a fully functional implementation */
+ private static class TestHostLivenessTracker implements HostLivenessTracker {
+
+ private final Clock clock;
+ private Instant constructionTime;
+ private final Map<String, Instant> lastRequestFromHost = new HashMap<>();
+
+ public TestHostLivenessTracker(Clock clock) {
+ this.clock = clock;
+ this.constructionTime = clock.instant();
+ }
+
+ public void setConstructedNow() {
+ constructionTime = clock.instant();
+ lastRequestFromHost.clear();
+ }
+
+ @Override
+ public Instant remembersRequestsSince() { return constructionTime; }
+
+ @Override
+ public void receivedRequestFrom(String hostname) {
+ lastRequestFromHost.put(hostname, clock.instant());
+ }
+
+ @Override
+ public Optional<Instant> lastRequestFrom(String hostname) {
+ return Optional.ofNullable(lastRequestFromHost.get(hostname));
+ }
+
+ }
+
private static class ServiceMonitorStub implements ServiceMonitor {
private final Map<ApplicationId, MockDeployer.ApplicationContext> apps;
@@ -343,8 +432,8 @@ public class NodeFailerTest {
@Override
public ApplicationInstanceStatus getApplicationInstanceStatus(ApplicationId appId) throws ApplicationIdNotFoundException {
- return suspendedApplications.contains(appId) ? ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN :
- ApplicationInstanceStatus.NO_REMARKS;
+ return suspendedApplications.contains(appId)
+ ? ApplicationInstanceStatus.ALLOWED_TO_BE_DOWN : ApplicationInstanceStatus.NO_REMARKS;
}
@Override
@@ -367,4 +456,5 @@ public class NodeFailerTest {
throw new RuntimeException("Not implemented");
}
}
+
}