diff options
author | Jon Bratseth <bratseth@oath.com> | 2018-08-29 22:47:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-08-29 22:47:39 +0200 |
commit | 03efdabdc9f0e43fc2c362569ae455a7abc9a64b (patch) | |
tree | a66e3afacaf3d6ed7dc2046bb61aefbe51478376 | |
parent | dbc86be22dad1d9fb279d978a45e621a5325ca7e (diff) | |
parent | 5703f2a5fe3af2cea95cc34faa74bd10fa918e2d (diff) |
Merge pull request #6719 from vespa-engine/bratseth/initially-down
Be down initially
9 files changed, 178 insertions, 109 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 4c5dafb7d8f..e6fd44b5e8b 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -258,6 +258,7 @@ public final class ContainerCluster addSimpleComponent("com.yahoo.container.protect.FreezeDetector"); addSimpleComponent("com.yahoo.container.core.slobrok.SlobrokConfigurator"); addSimpleComponent("com.yahoo.container.handler.VipStatus"); + addSimpleComponent(com.yahoo.container.handler.ClustersStatus.class.getName()); addJaxProviders(); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java index 46be61964ce..7ec93c0cf98 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java @@ -39,7 +39,6 @@ import java.util.concurrent.TimeUnit; public class ConfigServerBootstrap extends AbstractComponent implements Runnable { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(ConfigServerBootstrap.class.getName()); - private static final String vipStatusClusterIdentifier = "configserver"; enum MainThread {START, DO_NOT_START} enum RedeployingApplicationsFails {EXIT_JVM, CONTINUE} @@ -140,18 +139,18 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable private void up() { stateMonitor.status(StateMonitor.Status.up); - vipStatus.addToRotation(vipStatusClusterIdentifier); + vipStatus.setInRotation(true); } private void down() { stateMonitor.status(StateMonitor.Status.down); - vipStatus.removeFromRotation(vipStatusClusterIdentifier); + vipStatus.setInRotation(false); } private void initializing() { // This is default value (from config), so not strictly necessary stateMonitor.status(StateMonitor.Status.initializing); - vipStatus.removeFromRotation(vipStatusClusterIdentifier); + vipStatus.setInRotation(false); } private void startRpcServer() { diff --git a/container-core/src/main/java/com/yahoo/container/handler/ClustersStatus.java b/container-core/src/main/java/com/yahoo/container/handler/ClustersStatus.java new file mode 100644 index 00000000000..19617b46a84 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/ClustersStatus.java @@ -0,0 +1,75 @@ +package com.yahoo.container.handler; + +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; + +import java.util.HashMap; +import java.util.Map; + +/** + * A component which tracks the up/down status of any clusters which should influence + * the up down status of this container itself, as well as the separate fact that such clusters are present. + * + * This is a separate component which has <b>no dependencies</b> such that the status tracked in this + * will survive reconfiguration events and inform other components even immediately after a reconfiguration + * (where the true statue of clusters may not yet be available). + * + * @author bratseth + */ +public class ClustersStatus extends AbstractComponent { + + // NO DEPENDENCIES: Do not add dependencies here + @Inject + public ClustersStatus() { } + + /** Are there any (in-service influencing) clusters in this container? */ + private boolean containerHasClusters; + + /** If we have no clusters, what should we answer? */ + private boolean receiveTrafficByDefault; + + private final Object mutex = new Object(); + + /** The status of clusters, when known. Note that clusters may exist for which there is no knowledge yet. */ + private final Map<Object, Boolean> clusterStatus = new HashMap<>(); + + public void setContainerHasClusters(boolean containerHasClusters) { + synchronized (mutex) { + this.containerHasClusters = containerHasClusters; + if ( ! containerHasClusters) + clusterStatus.clear(); // forget container clusters which was configured away + } + } + + public void setReceiveTrafficByDefault(boolean receiveTrafficByDefault) { + synchronized (mutex) { + this.receiveTrafficByDefault = receiveTrafficByDefault; + } + } + + public void setUp(Object clusterIdentifier) { + synchronized (mutex) { + clusterStatus.put(clusterIdentifier, Boolean.TRUE); + } + } + + public void setDown(Object clusterIdentifier) { + synchronized (mutex) { + clusterStatus.put(clusterIdentifier, Boolean.FALSE); + } + } + + /** Returns whether this container should receive traffic based on the state of this */ + public boolean containerShouldReceiveTraffic() { + synchronized (mutex) { + if (containerHasClusters) { + // Should receive traffic when at least one cluster is up + return clusterStatus.values().stream().anyMatch(status -> status==true); + } + else { + return receiveTrafficByDefault; + } + } + } + +} diff --git a/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java b/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java index d7457140dae..be386b1b84e 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java +++ b/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java @@ -15,70 +15,56 @@ import com.yahoo.container.core.VipStatusConfig; */ public class VipStatus { - private final Map<Object, Boolean> clusters = new IdentityHashMap<>(); - private final VipStatusConfig vipStatusConfig; + private final ClustersStatus clustersStatus; + + /** If this is non-null, its value decides whether this container is in rotation */ + private Boolean inRotationOverride; public VipStatus() { - this(null, new VipStatusConfig(new VipStatusConfig.Builder())); + this(new QrSearchersConfig(new QrSearchersConfig.Builder()), + new VipStatusConfig(new VipStatusConfig.Builder()), + new ClustersStatus()); } public VipStatus(QrSearchersConfig dispatchers) { - this(dispatchers, new VipStatusConfig(new VipStatusConfig.Builder())); + this(dispatchers, new VipStatusConfig(new VipStatusConfig.Builder()), new ClustersStatus()); + } + + public VipStatus(ClustersStatus clustersStatus) { + this.clustersStatus = clustersStatus; } - // TODO: Why use QrSearchersConfig here? Remove and inject ComponentRegistry<ClusterSearcher> instead? @Inject - public VipStatus(QrSearchersConfig dispatchers, VipStatusConfig vipStatusConfig) { - // the config is not used for anything, it's just a dummy to create a - // dependency link to which dispatchers are used - this.vipStatusConfig = vipStatusConfig; + public VipStatus(QrSearchersConfig dispatchers, VipStatusConfig vipStatusConfig, ClustersStatus clustersStatus) { + this.clustersStatus = clustersStatus; + clustersStatus.setReceiveTrafficByDefault(vipStatusConfig.initiallyInRotation()); + clustersStatus.setContainerHasClusters(! dispatchers.searchcluster().isEmpty()); } /** - * Set a service or cluster into rotation. + * Explicitly set this container in or out of rotation * - * @param clusterIdentifier - * an object where the object identity will serve to identify the - * cluster or service + * @param inRotation true to set this in rotation regardless of any clusters and of the default value, + * false to set it out, and null to make this decision using the usual cluster-dependent logic */ + public void setInRotation(Boolean inRotation) { + this.inRotationOverride = inRotation; + } + + /** Note that a cluster (which influences up/down state) is up */ public void addToRotation(Object clusterIdentifier) { - synchronized (clusters) { - clusters.put(clusterIdentifier, Boolean.TRUE); - } + clustersStatus.setUp(clusterIdentifier); } - /** - * Set a service or cluster out of rotation. - * - * @param clusterIdentifier - * an object where the object identity will serve to identify the - * cluster or service - */ + /** Note that a cluster (which influences up/down state) is down */ public void removeFromRotation(Object clusterIdentifier) { - synchronized (clusters) { - clusters.put(clusterIdentifier, Boolean.FALSE); - } + clustersStatus.setDown(clusterIdentifier); } - /** - * Tell whether the container is connected to any active services at all. - * - * @return true if at least one service or cluster is up, or value is taken from config if no services - * are registered (yet) - */ + /** Returns whether this container should receive traffic at this time */ public boolean isInRotation() { - synchronized (clusters) { - // if no stored state, use config to decide whether to serve or not - if (clusters.size() == 0) { - return vipStatusConfig.initiallyInRotation(); - } - for (Boolean inRotation : clusters.values()) { - if (inRotation) { - return true; - } - } - } - return false; + if (inRotationOverride != null) return inRotationOverride; + return clustersStatus.containerShouldReceiveTraffic(); } } diff --git a/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java b/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java index b7977e7832d..60affddeb60 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java +++ b/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java @@ -49,7 +49,7 @@ public final class VipStatusHandler extends ThreadedHttpRequestHandler { class StatusResponse extends HttpResponse { static final String COULD_NOT_FIND_STATUS_FILE = "Could not find status file.\n"; - static final String NO_SEARCH_BACKENDS = "No search backends available, VIP status disabled."; + static final String NO_SEARCH_BACKENDS = "No search backends available, VIP status disabled."; // TODO: Generify private static final String TEXT_HTML = "text/html"; private String contentType = TEXT_HTML; private byte[] data = null; diff --git a/container-core/src/main/resources/configdefinitions/vip-status.def b/container-core/src/main/resources/configdefinitions/vip-status.def index 1e364419ab8..6aa10ff1b90 100644 --- a/container-core/src/main/resources/configdefinitions/vip-status.def +++ b/container-core/src/main/resources/configdefinitions/vip-status.def @@ -4,6 +4,7 @@ namespace=container.core ## If there is a Vespa search backend connected to this container, and that ## backend is out of service, automatically remove this container from VIP ## rotation, ignoring any status file. +## TODO VESPA 7: This is always true and can be removed noSearchBackendsImpliesOutOfService bool default=true ## Whether to return hard-coded reply or serve "status.html" from disk @@ -13,5 +14,5 @@ accessdisk bool default=false ## If the path is relative vespa home is prepended statusfile string default="share/qrsdocs/status.html" -## The initial rotation state when no information is known about backend clusters +## The default rotation state when there are no configured clusters to decide rotation state initiallyInRotation bool default=true diff --git a/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java index ef51a3c0f51..f8de32ee3ff 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java +++ b/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java @@ -35,16 +35,16 @@ import static org.junit.Assert.fail; * care about the incoming URI, that's 100% handled in JDIsc by the binding * pattern. * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + * @author Steinar Knutsen */ public class VipStatusHandlerTestCase { - public static final class MockResponseHandler implements ResponseHandler { + public static class MockResponseHandler implements ResponseHandler { + final ReadableContentChannel channel = new ReadableContentChannel(); @Override - public ContentChannel handleResponse( - final com.yahoo.jdisc.Response response) { + public ContentChannel handleResponse(com.yahoo.jdisc.Response response) { return channel; } } @@ -52,45 +52,44 @@ public class VipStatusHandlerTestCase { Metric metric = Mockito.mock(Metric.class); @Test - public final void testHandleRequest() { - final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) + public void testHandleRequest() { + VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) .noSearchBackendsImpliesOutOfService(false)); - final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); - final MockResponseHandler responseHandler = new MockResponseHandler(); - final HttpRequest request = createRequest(); - final BufferedContentChannel requestContent = createChannel(); + VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + MockResponseHandler responseHandler = new MockResponseHandler(); + HttpRequest request = createRequest(); + BufferedContentChannel requestContent = createChannel(); handler.handleRequest(request, requestContent, responseHandler); - final ByteBuffer b = responseHandler.channel.read(); - final byte[] asBytes = new byte[b.remaining()]; + ByteBuffer b = responseHandler.channel.read(); + byte[] asBytes = new byte[b.remaining()]; b.get(asBytes); assertEquals(VipStatusHandler.OK_MESSAGE, Utf8.toString(asBytes)); } - public static final class NotFoundResponseHandler implements - ResponseHandler { + public static class NotFoundResponseHandler implements ResponseHandler { + final ReadableContentChannel channel = new ReadableContentChannel(); @Override - public ContentChannel handleResponse( - final com.yahoo.jdisc.Response response) { - assertEquals(com.yahoo.jdisc.Response.Status.NOT_FOUND, - response.getStatus()); + public ContentChannel handleResponse(com.yahoo.jdisc.Response response) { + assertEquals(com.yahoo.jdisc.Response.Status.NOT_FOUND, response.getStatus()); return channel; } + } @Test - public final void testFileNotFound() { - final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) + public void testFileNotFound() { + VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) .statusfile("/VipStatusHandlerTestCaseFileThatReallyReallyShouldNotExist") .noSearchBackendsImpliesOutOfService(false)); - final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); - final NotFoundResponseHandler responseHandler = new NotFoundResponseHandler(); - final HttpRequest request = createRequest(); - final BufferedContentChannel requestContent = createChannel(); + VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + NotFoundResponseHandler responseHandler = new NotFoundResponseHandler(); + HttpRequest request = createRequest(); + BufferedContentChannel requestContent = createChannel(); handler.handleRequest(request, requestContent, responseHandler); - final ByteBuffer b = responseHandler.channel.read(); - final byte[] asBytes = new byte[b.remaining()]; + ByteBuffer b = responseHandler.channel.read(); + byte[] asBytes = new byte[b.remaining()]; b.get(asBytes); assertEquals( VipStatusHandler.StatusResponse.COULD_NOT_FIND_STATUS_FILE, @@ -98,23 +97,22 @@ public class VipStatusHandlerTestCase { } @Test - public final void testFileFound() throws IOException { - final File statusFile = File.createTempFile("VipStatusHandlerTestCase", - null); + public void testFileFound() throws IOException { + File statusFile = File.createTempFile("VipStatusHandlerTestCase", null); try { - final FileWriter writer = new FileWriter(statusFile); - final String OK = "OK\n"; + FileWriter writer = new FileWriter(statusFile); + String OK = "OK\n"; writer.write(OK); writer.close(); - final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) + VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) .statusfile(statusFile.getAbsolutePath()).noSearchBackendsImpliesOutOfService(false)); - final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); - final MockResponseHandler responseHandler = new MockResponseHandler(); - final HttpRequest request = createRequest(); - final BufferedContentChannel requestContent = createChannel(); + VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + MockResponseHandler responseHandler = new MockResponseHandler(); + HttpRequest request = createRequest(); + BufferedContentChannel requestContent = createChannel(); handler.handleRequest(request, requestContent, responseHandler); - final ByteBuffer b = responseHandler.channel.read(); - final byte[] asBytes = new byte[b.remaining()]; + ByteBuffer b = responseHandler.channel.read(); + byte[] asBytes = new byte[b.remaining()]; b.get(asBytes); assertEquals(OK, Utf8.toString(asBytes)); } finally { @@ -123,34 +121,34 @@ public class VipStatusHandlerTestCase { } @Test - public final void testProgrammaticallyRemovedFromRotation() throws IOException { + public void testExplicitlyRotationControl() { VipStatus vipStatus = new VipStatus(); - final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) + VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) .noSearchBackendsImpliesOutOfService(true)); - final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric, vipStatus); + VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric, vipStatus); - vipStatus.removeFromRotation(this); + vipStatus.setInRotation(false); { - final MockResponseHandler responseHandler = new MockResponseHandler(); - final HttpRequest request = createRequest(); - final BufferedContentChannel requestContent = createChannel(); + MockResponseHandler responseHandler = new MockResponseHandler(); + HttpRequest request = createRequest(); + BufferedContentChannel requestContent = createChannel(); handler.handleRequest(request, requestContent, responseHandler); - final ByteBuffer b = responseHandler.channel.read(); - final byte[] asBytes = new byte[b.remaining()]; + ByteBuffer b = responseHandler.channel.read(); + byte[] asBytes = new byte[b.remaining()]; b.get(asBytes); assertEquals(VipStatusHandler.StatusResponse.NO_SEARCH_BACKENDS, Utf8.toString(asBytes)); } - vipStatus.addToRotation(this); + vipStatus.setInRotation(true); { - final MockResponseHandler responseHandler = new MockResponseHandler(); - final HttpRequest request = createRequest(); - final BufferedContentChannel requestContent = createChannel(); + MockResponseHandler responseHandler = new MockResponseHandler(); + HttpRequest request = createRequest(); + BufferedContentChannel requestContent = createChannel(); handler.handleRequest(request, requestContent, responseHandler); - final ByteBuffer b = responseHandler.channel.read(); - final byte[] asBytes = new byte[b.remaining()]; + ByteBuffer b = responseHandler.channel.read(); + byte[] asBytes = new byte[b.remaining()]; b.get(asBytes); assertEquals(VipStatusHandler.OK_MESSAGE, Utf8.toString(asBytes)); } diff --git a/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java index 725f8256ba3..e54f968f41d 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java +++ b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java @@ -13,13 +13,22 @@ import org.junit.Test; public class VipStatusTestCase { @Test - public final void testSmoke() { + public void testVipStatusWorksWithClusters() { + ClustersStatus clustersStatus = new ClustersStatus(); + clustersStatus.setContainerHasClusters(true); + VipStatus v = new VipStatus(clustersStatus); + Object cluster1 = new Object(); Object cluster2 = new Object(); Object cluster3 = new Object(); - VipStatus v = new VipStatus(); + // initial state + assertFalse(v.isInRotation()); + + // one cluster becomes up + v.addToRotation(cluster1); assertTrue(v.isInRotation()); + // all clusters down v.removeFromRotation(cluster1); v.removeFromRotation(cluster2); diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/SearchCluster.java b/container-search/src/main/java/com/yahoo/search/dispatch/SearchCluster.java index efce2fdac9c..a4d7a54a80e 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/SearchCluster.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/SearchCluster.java @@ -182,7 +182,7 @@ public class SearchCluster implements NodeManager<SearchCluster.Node> { node.setWorking(true); if (usesDirectDispatchTo(node)) - vipStatus.addToRotation(this); + vipStatus.setInRotation(true); } /** Used by the cluster monitor to manage node status */ @@ -192,16 +192,16 @@ public class SearchCluster implements NodeManager<SearchCluster.Node> { // Take ourselves out if we usually dispatch only to our own host if (usesDirectDispatchTo(node)) - vipStatus.removeFromRotation(this); + vipStatus.setInRotation(false); } private void updateSufficientCoverage(Group group, boolean sufficientCoverage) { // update VIP status if we direct dispatch to this group and coverage status changed if (usesDirectDispatchTo(group) && sufficientCoverage != group.hasSufficientCoverage()) { if (sufficientCoverage) { - vipStatus.addToRotation(this); + vipStatus.setInRotation(true); } else { - vipStatus.removeFromRotation(this); + vipStatus.setInRotation(false); } } group.setHasSufficientCoverage(sufficientCoverage); |