diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /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.java | 195 |
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."); + } + } + } + +} |