summaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java
Publish
Diffstat (limited to 'container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java')
-rw-r--r--container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java195
1 files changed, 195 insertions, 0 deletions
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
new file mode 100644
index 00000000000..3be1d08c5dc
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/handler/VipStatusHandler.java
@@ -0,0 +1,195 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.handler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+import com.yahoo.container.core.VipStatusConfig;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.log.LogLevel;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.defaults.Defaults;
+
+/**
+ * Transmit status to VIP from file or memory. Bind this to
+ * "http://{@literal *}/status.html" to serve VIP status requests.
+ *
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public final class VipStatusHandler extends ThreadedHttpRequestHandler {
+
+ private static final Logger log = Logger.getLogger(VipStatusHandler.class.getName());
+
+ private static final String NUM_REQUESTS_METRIC = "jdisc.http.requests.status";
+
+ private final boolean accessDisk;
+ private final File statusFile;
+ private final VipStatus vipStatus;
+ private final boolean noSearchBackendsImpliesOutOfService;
+
+ private volatile boolean previouslyInRotation = true;
+
+ // belongs in the response, but that's not a static class
+ static final String OK_MESSAGE = "<title>OK</title>\n";
+ static final byte[] VIP_OK = Utf8.toBytes(OK_MESSAGE);
+
+ 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.";
+ private static final String TEXT_HTML = "text/html";
+ private String contentType = TEXT_HTML;
+ private byte[] data = null;
+ private File file = null;
+
+ private StatusResponse() {
+ super(com.yahoo.jdisc.http.HttpResponse.Status.OK); // status may be overwritten below
+ if (noSearchBackendsImpliesOutOfService && !vipStatus.isInRotation()) {
+ searchContainerOutOfService();
+ } else if (accessDisk) {
+ preSlurpFile();
+ } else {
+ vipRespond();
+ }
+ }
+
+ @Override
+ public void render(OutputStream stream) throws IOException {
+ if (file != null) {
+ readAndWrite(stream);
+ }
+ else if (data != null) {
+ stream.write(data);
+ }
+ else {
+ throw new IllegalStateException(
+ "Neither file nor hardcoded data. This is a bug, please notify the Vespa team.");
+ }
+ stream.close();
+ }
+
+ private void readAndWrite(OutputStream stream) throws IOException {
+ InputStream input;
+ int lastRead = 0;
+ input = new FileInputStream(file);
+ try {
+ while (lastRead != -1) {
+ byte[] buffer = new byte[5000];
+ lastRead = input.read(buffer);
+ if (lastRead > 0) {
+ stream.write(buffer, 0, lastRead);
+ }
+ }
+ } finally {
+ stream.close();
+ input.close();
+ }
+ }
+
+ private void preSlurpFile() {
+ try {
+ if (!statusFile.exists()) {
+ fileNotFound();
+ return;
+ }
+ if (!statusFile.canRead()) {
+ accessDenied();
+ return;
+ }
+ } catch (SecurityException e) {
+ internalError();
+ return;
+ }
+ this.file = statusFile;
+ }
+
+ private void accessDenied() {
+ contentType = "text/plain";
+ data = Utf8.toBytes("Status file inaccessible.\n");
+ setStatus(com.yahoo.jdisc.http.HttpResponse.Status.NOT_FOUND);
+ }
+
+ private void internalError() {
+ contentType = "text/plain";
+ data = Utf8.toBytes("Internal error while fetching status file.\n");
+ setStatus(com.yahoo.jdisc.http.HttpResponse.Status.NOT_FOUND);
+ }
+
+ private void fileNotFound() {
+ contentType = "text/plain";
+ data = Utf8.toBytes(COULD_NOT_FIND_STATUS_FILE);
+ setStatus(com.yahoo.jdisc.http.HttpResponse.Status.NOT_FOUND);
+ }
+
+ private void vipRespond() {
+ data = VIP_OK;
+ }
+
+ /**
+ * Behaves like a VIP status response file has been deleted.
+ */
+ private void searchContainerOutOfService() {
+ contentType = "text/plain";
+ data = Utf8.toBytes(NO_SEARCH_BACKENDS);
+ setStatus(com.yahoo.jdisc.http.HttpResponse.Status.NOT_FOUND);
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+ }
+
+ public VipStatusHandler(Executor executor, VipStatusConfig vipConfig, Metric metric) {
+ this(executor, vipConfig, metric, null);
+ }
+
+ @Inject
+ public VipStatusHandler(Executor executor, VipStatusConfig vipConfig, Metric metric, VipStatus vipStatus) {
+ super(executor, metric);
+ this.accessDisk = vipConfig.accessdisk();
+ this.statusFile = new File(Defaults.getDefaults().underVespaHome(vipConfig.statusfile()));
+ this.noSearchBackendsImpliesOutOfService = vipConfig.noSearchBackendsImpliesOutOfService();
+ this.vipStatus = vipStatus;
+ }
+
+ @Override
+ public HttpResponse handle(HttpRequest request) {
+ if (metric != null)
+ metric.add(NUM_REQUESTS_METRIC, 1, null);
+ if (noSearchBackendsImpliesOutOfService) {
+ updateAndLogRotationState();
+ }
+ return new StatusResponse();
+ }
+
+ private void updateAndLogRotationState() {
+ final boolean currentlyInRotation = vipStatus.isInRotation();
+ final boolean previousRotationAnswer = previouslyInRotation;
+ previouslyInRotation = currentlyInRotation;
+
+ if (previousRotationAnswer != currentlyInRotation) {
+ if (currentlyInRotation) {
+ log.log(LogLevel.INFO, "Putting container back into rotation by serving status.html again.");
+ } else {
+ log.log(LogLevel.WARNING, "Removing container from rotation by no longer serving status.html.");
+ }
+ }
+ }
+
+}