diff options
author | Ola Aunrønning <ola.aunroe@gmail.com> | 2018-08-24 14:54:27 +0200 |
---|---|---|
committer | Ola Aunrønning <ola.aunroe@gmail.com> | 2018-09-10 13:07:15 +0200 |
commit | 44053e445e8f893514667325be40613830056e6e (patch) | |
tree | bd1f6be2b7ec2f419d2a9363de96fecb32988152 | |
parent | 79fbe75a324084d7e871e4aa7b82500e7ccd35b3 (diff) |
Add functionality for retrieving logs
15 files changed, 264 insertions, 1 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index a8b4844ca43..585a1a67f4d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -36,6 +36,7 @@ import com.yahoo.vespa.config.server.configchange.RestartActions; import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger; import com.yahoo.vespa.config.server.deploy.Deployment; import com.yahoo.vespa.config.server.http.CompressedApplicationInputStream; +import com.yahoo.vespa.config.server.http.LogRetriever; import com.yahoo.vespa.config.server.http.SimpleHttpFetcher; import com.yahoo.vespa.config.server.http.v2.PrepareResult; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; @@ -50,6 +51,7 @@ import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.config.server.tenant.Rotations; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.model.VespaModel; import java.io.File; import java.io.IOException; @@ -477,6 +479,13 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return convergeChecker.servicesToCheck(getApplication(applicationId), uri, timeout); } + // ---------------- Logs ---------------------------------------------------------------- + + public HttpResponse getLogs(ApplicationId applicationId) { + String logServerHostName = getLogServerHostname(applicationId); + return LogRetriever.getLogs(logServerHostName); + } + // ---------------- Session operations ---------------------------------------------------------------- /** @@ -690,6 +699,13 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye } } + private String getLogServerHostname(ApplicationId applicationId) { + Application application = getApplication(applicationId); + VespaModel model = (VespaModel) application.getModel(); + String logServerHostname = model.getAdmin().getLogserver().getHostName(); + return logServerHostname; + } + /** Returns version to use when deploying application in given environment */ static Version decideVersion(ApplicationId application, Environment environment, Version targetVersion, boolean bootstrap) { if (environment.isManuallyDeployed() && diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/LogRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/LogRetriever.java new file mode 100644 index 00000000000..f4952b5b9fc --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/LogRetriever.java @@ -0,0 +1,41 @@ +package com.yahoo.vespa.config.server.http; + +import com.yahoo.container.jdisc.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class LogRetriever { + + private final static Logger log = Logger.getLogger(LogRetriever.class.getName()); + + public static HttpResponse getLogs(String logServerHostname) { + HttpGet get = new HttpGet(logServerHostname); + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + org.apache.http.HttpResponse response = httpClient.execute(get); + String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8"); + return new HttpResponse(response.getStatusLine().getStatusCode()) { + @Override + public void render(OutputStream outputStream) throws IOException { + if (response.getEntity() != null ) outputStream.write(responseBody.getBytes()); + } + }; + } catch (IOException e) { + log.log(Level.WARNING, "Failed to retrieve logs from log server", e); + return new HttpResponse(404) { + @Override + public void render(OutputStream outputStream) throws IOException { + outputStream.write(e.toString().getBytes()); + } + }; + } + + } +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java index 2004ab95144..b65cb370f93 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java @@ -96,6 +96,10 @@ public class ApplicationHandler extends HttpHandler { return applicationRepository.filedistributionStatus(applicationId, timeout); } + if (isLogRequest(request)) { + return applicationRepository.getLogs(applicationId); + } + return new GetApplicationResponse(Response.Status.OK, applicationRepository.getApplicationGeneration(applicationId)); } @@ -140,7 +144,13 @@ public class ApplicationHandler extends HttpHandler { "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/serviceconverge/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*/clustercontroller/*/status/*", "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*", - "http://*/application/v2/tenant/*/application/*"); + "http://*/application/v2/tenant/*/application/*", + "http://*/application/v2/tenant/*/application/*/logs"); + } + + private static boolean isLogRequest(HttpRequest request) { + return getBindingMatch(request).groupCount() == 4 && + request.getUri().getPath().endsWith("/logs"); } private static boolean isServiceConvergeListRequest(HttpRequest request) { diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index 8a99869e69a..60dd7b0cea2 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -147,6 +147,8 @@ <binding>https://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*</binding> <binding>http://*/application/v2/tenant/*/application/*</binding> <binding>https://*/application/v2/tenant/*/application/*</binding> + <binding>http://*/application/v2/tenant/*/application/*/logs</binding> + <binding>https://*/application/v2/tenant/*/application/*/logs</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.HttpGetConfigHandler' bundle='configserver'> <binding>http://*/config/v2/tenant/*/application/*/*</binding> diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/LogRetrieverTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/LogRetrieverTest.java new file mode 100644 index 00000000000..2c3622e3dac --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/LogRetrieverTest.java @@ -0,0 +1,46 @@ +package com.yahoo.vespa.config.server.http; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.yahoo.container.jdisc.HttpResponse; +import org.junit.Rule; +import org.junit.Test; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + +public class LogRetrieverTest { + + private String logServerHostName = "http://localhost:8080/"; + @Rule + public final WireMockRule wireMock = new WireMockRule(options().port(8080), true); + + @Test + public void testThatLogHandlerPropagatesResponseBody() throws IOException { + String expectedBody = "{logs-json}"; + stubFor(get(urlEqualTo("/")).willReturn(okJson(expectedBody))); + HttpResponse response = LogRetriever.getLogs(logServerHostName); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + response.render(byteArrayOutputStream); + assertEquals(expectedBody, byteArrayOutputStream.toString()); + assertEquals(200, response.getStatus()); + } + + @Test + public void testThatNotFoundLogServerReturns404() throws IOException { + stubFor(get(urlEqualTo("/")).willReturn(aResponse().withStatus(200))); + HttpResponse response = LogRetriever.getLogs("http://wrong-host:8080/"); + assertEquals(404, response.getStatus()); + } + + + +}
\ No newline at end of file diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java new file mode 100644 index 00000000000..474d7d7321a --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java @@ -0,0 +1,31 @@ +package com.yahoo.container.handler; + +import com.fasterxml.jackson.core.JsonFactory; +import com.google.inject.Inject; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.Executor; + +public class LogHandler extends ThreadedHttpRequestHandler { + + private static final String LOG_DIRECTORY = "/home/y/logs/vespa/"; + + @Inject + public LogHandler(Executor executor) { + super(executor); + } + + @Override + public HttpResponse handle(HttpRequest request) { + + return new HttpResponse(200) { + @Override + public void render(OutputStream outputStream) throws IOException { + LogReader.writeToOutputStream(LOG_DIRECTORY, outputStream); + } + }; + } +} diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java new file mode 100644 index 00000000000..e35ea748c0e --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java @@ -0,0 +1,44 @@ +package com.yahoo.container.handler; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import org.apache.commons.codec.binary.Base64; +import org.json.JSONException; +import org.json.JSONObject; + +import javax.xml.bind.DatatypeConverter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.file.Files; + +public class LogReader { + + protected static void writeToOutputStream(String logDirectory, OutputStream outputStream) throws IOException { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); + JSONObject json = new JSONObject(); + File root = new File(logDirectory); + try { + traverse_folder(root, json); + } catch (JSONException e) { + outputStreamWriter.write("Failed to create log JSON"); + } + outputStreamWriter.write(json.toString()); + outputStreamWriter.close(); + } + + private static void traverse_folder(File root, JSONObject json) throws IOException, JSONException { + for(File child : root.listFiles()) { + JSONObject childJson = new JSONObject(); + if(child.isFile()) { + json.put(child.getName(), DatatypeConverter.printBase64Binary(Files.readAllBytes(child.toPath()))); + } + else { + json.put(child.getName(), childJson); + traverse_folder(child, childJson); + } + } + } +} diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java new file mode 100644 index 00000000000..ff6ea74a411 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java @@ -0,0 +1,36 @@ +package com.yahoo.container.handler; + +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; + +import static org.junit.Assert.*; + +public class LogReaderTest { + + ByteArrayOutputStream outputStream; + + @Before + public void setup() { + outputStream = new ByteArrayOutputStream(); + } + + @Test + public void testThatFilesAreWrittenCorrectlyToOutputStream() throws Exception{ + String logDirectory = "src/test/resources/logfolder/"; + LogReader.writeToOutputStream(logDirectory, outputStream); + String expected = "{\"subfolder\":{\"log2.log\":\"VGhpcyBpcyBhbm90aGVyIGxvZyBmaWxl\"},\"log1.log\":\"VGhpcyBpcyBvbmUgbG9nIGZpbGU=\"}"; + String actual = new String(outputStream.toByteArray()); + assertEquals(expected, actual); + } + + @Test + public void testNothingISWrittenToOutputStreamWithEmptyLogFolder() throws Exception { + String logDirectory = "src/test/resources/emptylogfolder/"; + LogReader.writeToOutputStream(logDirectory, outputStream); + String expected = "{}"; + String actual = new String(outputStream.toByteArray()); + assertEquals(expected, actual); + } +}
\ No newline at end of file diff --git a/container-core/src/test/resources/logfolder/log1.log b/container-core/src/test/resources/logfolder/log1.log new file mode 100644 index 00000000000..bb85d5a4950 --- /dev/null +++ b/container-core/src/test/resources/logfolder/log1.log @@ -0,0 +1 @@ +This is one log file
\ No newline at end of file diff --git a/container-core/src/test/resources/logfolder/subfolder/log2.log b/container-core/src/test/resources/logfolder/subfolder/log2.log new file mode 100644 index 00000000000..aee6eaca2e8 --- /dev/null +++ b/container-core/src/test/resources/logfolder/subfolder/log2.log @@ -0,0 +1 @@ +This is another log file
\ No newline at end of file diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java index 54e057e4187..eb10c78f891 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java @@ -1,6 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.configserver; +import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; @@ -41,6 +42,7 @@ public interface ConfigServer { Map<?,?> getServiceApiResponse(String tenantName, String applicationName, String instanceName, String environment, String region, String serviceName, String restPath); + HttpResponse getLogs(DeploymentId deployment); /** * Set new status on en endpoint in one zone. * diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 22809ac18bf..556d019d440 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -169,6 +169,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), request); + if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region")); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri()); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)), request.getUri()); if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/run/{number}")) return JobControllerApiHandlerHelper.runDetailsResponse(controller.jobController(), runIdFromPath(path), request.getProperty("after")); @@ -346,6 +347,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new SlimeJsonResponse(slime); } + private HttpResponse logs(String tenantName, String applicationName, String instanceName, String environment, String region) { + ApplicationId application = ApplicationId.from(tenantName, applicationName, instanceName); + ZoneId zone = ZoneId.from(environment, region); + DeploymentId deployment = new DeploymentId(application, zone); + return controller.configServer().getLogs(deployment); + } + + private void toSlime(Cursor object, Application application, HttpRequest request) { object.setString("application", application.id().application().value()); object.setString("instance", application.id().instance().value()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java index 458ba49f3e3..bd65465633e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java @@ -7,6 +7,7 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeType; +import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus; import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions; @@ -25,7 +26,11 @@ import com.yahoo.vespa.hosted.controller.application.SystemApplication; import com.yahoo.vespa.serviceview.bindings.ApplicationView; import com.yahoo.vespa.serviceview.bindings.ClusterView; import com.yahoo.vespa.serviceview.bindings.ServiceView; +import org.json.JSONException; +import org.json.JSONObject; +import java.io.IOException; +import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -292,6 +297,17 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer return endpoints.getOrDefault(endpoint, result); } + @Override + public HttpResponse getLogs(DeploymentId deployment) { + return new HttpResponse(200) { + @Override + public void render(OutputStream outputStream) throws IOException { + outputStream.write("{\"subfolder\":{\"log2.log\":\"VGhpcyBpcyBhbm90aGVyIGxvZyBmaWxl\"},\"log1.log\":\"VGhpcyBpcyBvbmUgbG9nIGZpbGU=\"}".getBytes()); + } + }; + + } + public static class Application { private final ApplicationId id; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 13092451d4b..03877cca30e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -330,6 +330,9 @@ public class ApplicationApiTest extends ControllerContainerTest { .recursive("true"), new File("application1-recursive.json")); + // GET logs + tester.assertResponse(request("/application/v4/tenant/tenant2/application//application1/environment/prod/region/corp-us-east-1/instance/default/logs", GET).userIdentity(USER_ID), new File("logs.json")); + // DELETE (cancel) ongoing change tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE) .userIdentity(HOSTED_VESPA_OPERATOR), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/logs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/logs.json new file mode 100644 index 00000000000..398a62758ee --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/logs.json @@ -0,0 +1,5 @@ +{ + "subfolder": { + "log2.log":"VGhpcyBpcyBhbm90aGVyIGxvZyBmaWxl"}, + "log1.log":"VGhpcyBpcyBvbmUgbG9nIGZpbGU=" +}
\ No newline at end of file |