This is an automated email from the ASF dual-hosted git repository. xiangfu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push: new 70ecda4952 Adding endpoint to download local log files for each component (#9259) 70ecda4952 is described below commit 70ecda495281a6922b725c182f27cb010530b5f2 Author: Xiang Fu <xiangfu.1...@gmail.com> AuthorDate: Fri Aug 26 19:08:09 2022 -0700 Adding endpoint to download local log files for each component (#9259) --- .../broker/api/resources/PinotBrokerLogger.java | 35 ++++++ .../broker/broker/BrokerAdminApiApplication.java | 6 + .../pinot/common/utils/LoggerFileServer.java | 76 ++++++++++++ .../pinot/common/utils/config/InstanceUtils.java | 21 ++++ .../apache/pinot/common/utils/http/HttpClient.java | 2 +- .../pinot/common/utils/LoggerFileServerTest.java | 79 ++++++++++++ .../pinot/controller/BaseControllerStarter.java | 5 + .../api/resources/PinotControllerLogger.java | 138 +++++++++++++++++++++ .../pinot/minion/MinionAdminApiApplication.java | 5 + .../minion/api/resources/PinotMinionLogger.java | 35 ++++++ .../server/api/resources/PinotServerLogger.java | 35 ++++++ .../server/starter/helix/AdminApiApplication.java | 5 + .../apache/pinot/spi/utils/CommonConstants.java | 5 + 13 files changed, 446 insertions(+), 1 deletion(-) diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java index acdaf3cbbb..849807f42d 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java @@ -25,8 +25,11 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -37,6 +40,7 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.LoggerUtils; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -51,6 +55,9 @@ import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_K @Path("/") public class PinotBrokerLogger { + @Inject + private LoggerFileServer _loggerFileServer; + @GET @Path("/loggers") @Produces(MediaType.APPLICATION_JSON) @@ -80,4 +87,32 @@ public class PinotBrokerLogger { @ApiParam(value = "Logger level") @QueryParam("level") String level) { return LoggerUtils.setLoggerLevel(loggerName, level); } + + @GET + @Path("/loggers/files") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get all local log files") + public Set<String> getLocalLogFiles() { + try { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.getAllPaths(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Path("/loggers/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @ApiOperation(value = "Download a log file") + public Response downloadLogFile( + @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath) { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory is not configured", + Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.downloadLogFile(filePath); + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java index 3978b65891..1a43f7cf25 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java @@ -32,6 +32,7 @@ import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.pinot.broker.requesthandler.BrokerRequestHandler; import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.core.api.ServiceAutoDiscoveryFeature; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; import org.apache.pinot.core.transport.ListenerConfig; @@ -51,6 +52,7 @@ public class BrokerAdminApiApplication extends ResourceConfig { private static final String RESOURCE_PACKAGE = "org.apache.pinot.broker.api.resources"; public static final String PINOT_CONFIGURATION = "pinotConfiguration"; public static final String BROKER_INSTANCE_ID = "brokerInstanceId"; + private final boolean _useHttps; private HttpServer _httpServer; @@ -78,6 +80,10 @@ public class BrokerAdminApiApplication extends ResourceConfig { bind(routingManager).to(BrokerRoutingManager.class); bind(brokerRequestHandler).to(BrokerRequestHandler.class); bind(brokerMetrics).to(BrokerMetrics.class); + String loggerRootDir = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_LOGGER_ROOT_DIR); + if (loggerRootDir != null) { + bind(new LoggerFileServer(loggerRootDir)).to(LoggerFileServer.class); + } bind(brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_ID)).named(BROKER_INSTANCE_ID); } }); diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/LoggerFileServer.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/LoggerFileServer.java new file mode 100644 index 0000000000..b0eac3f946 --- /dev/null +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/LoggerFileServer.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.utils; + +import com.google.common.base.Preconditions; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.TreeSet; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; + + +/** + * Logger file server. + */ +public class LoggerFileServer { + private final File _loggerRootDir; + private final Path _loggerRootDirPath; + + public LoggerFileServer(String loggerRootDir) { + Preconditions.checkNotNull(loggerRootDir, "Logger root directory is null"); + _loggerRootDir = new File(loggerRootDir); + Preconditions.checkState(_loggerRootDir.exists(), "Logger directory doesn't exists"); + _loggerRootDirPath = Paths.get(_loggerRootDir.getAbsolutePath()); + } + + public Set<String> getAllPaths() + throws IOException { + Set<String> allFiles = new TreeSet<>(); + Files.walk(_loggerRootDirPath).filter(Files::isRegularFile).forEach( + f -> allFiles.add(f.toAbsolutePath().toString().replace(_loggerRootDirPath.toAbsolutePath() + "/", ""))); + return allFiles; + } + + public Response downloadLogFile(String filePath) { + try { + if (!getAllPaths().contains(filePath)) { + throw new WebApplicationException("Invalid file path: " + filePath, Response.Status.FORBIDDEN); + } + File logFile = new File(_loggerRootDir, filePath); + if (!logFile.exists()) { + throw new WebApplicationException("File: " + filePath + " doesn't exists", Response.Status.NOT_FOUND); + } + Response.ResponseBuilder builder = Response.ok(); + builder.entity(logFile); + builder.entity((StreamingOutput) output -> Files.copy(logFile.toPath(), output)); + builder.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + logFile.getName()); + builder.header(HttpHeaders.CONTENT_LENGTH, logFile.length()); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/InstanceUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/InstanceUtils.java index c840674390..07187fbd01 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/InstanceUtils.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/InstanceUtils.java @@ -25,6 +25,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.helix.model.InstanceConfig; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.helix.ExtraInstanceConfig; import org.apache.pinot.spi.config.instance.Instance; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Helix; @@ -157,4 +158,24 @@ public class InstanceUtils { mapFields.remove(POOL_KEY); } } + + public static String getInstanceBaseUri(InstanceConfig instanceConfig) { + Map<String, String> fieldMap = instanceConfig.getRecord().getSimpleFields(); + String hostName = instanceConfig.getHostName(); + String adminPort; + String scheme; + if (fieldMap.containsKey(CommonConstants.Helix.Instance.ADMIN_HTTPS_PORT_KEY)) { + // For Pinot Server admin https port + adminPort = fieldMap.get(CommonConstants.Helix.Instance.ADMIN_HTTPS_PORT_KEY); + scheme = "https"; + } else if (fieldMap.containsKey(ExtraInstanceConfig.PinotInstanceConfigProperty.PINOT_TLS_PORT.toString())) { + // For Pinot Controller/Broker TLS port + adminPort = fieldMap.get(ExtraInstanceConfig.PinotInstanceConfigProperty.PINOT_TLS_PORT.toString()); + scheme = "https"; + } else { + adminPort = fieldMap.getOrDefault(CommonConstants.Helix.Instance.ADMIN_PORT_KEY, instanceConfig.getPort()); + scheme = "http"; + } + return String.format("%s://%s:%s", scheme, hostName, adminPort); + } } diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java index d0179c3950..4bd7117ab8 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/http/HttpClient.java @@ -288,7 +288,7 @@ public class HttpClient implements AutoCloseable { } } - protected CloseableHttpResponse execute(HttpUriRequest request) + public CloseableHttpResponse execute(HttpUriRequest request) throws IOException { return _httpClient.execute(request); } diff --git a/pinot-common/src/test/java/org/apache/pinot/common/utils/LoggerFileServerTest.java b/pinot-common/src/test/java/org/apache/pinot/common/utils/LoggerFileServerTest.java new file mode 100644 index 0000000000..83ddc49dac --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/utils/LoggerFileServerTest.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.common.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import org.apache.commons.io.FileUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + + +public class LoggerFileServerTest { + + @Test + public void testLoggerFileServer() + throws IOException { + File logRootDir = new File(FileUtils.getTempDirectory(), "testGetAllLoggers-" + System.currentTimeMillis()); + try { + logRootDir.mkdirs(); + LoggerFileServer loggerFileServer = new LoggerFileServer(logRootDir.getAbsolutePath()); + + // Empty root log directory + assertEquals(loggerFileServer.getAllPaths().size(), 0); + try { + loggerFileServer.downloadLogFile("log1"); + Assert.fail("Shouldn't reach here"); + } catch (WebApplicationException e1) { + assertEquals(e1.getResponse().getStatus(), Response.Status.FORBIDDEN.getStatusCode()); + } + + // 1 file: [ log1 ] in root log directory + FileUtils.writeStringToFile(new File(logRootDir, "log1"), "mylog1", Charset.defaultCharset()); + assertEquals(loggerFileServer.getAllPaths().size(), 1); + assertNotNull(loggerFileServer.downloadLogFile("log1")); + try { + loggerFileServer.downloadLogFile("log2"); + Assert.fail("Shouldn't reach here"); + } catch (WebApplicationException e1) { + assertEquals(e1.getResponse().getStatus(), Response.Status.FORBIDDEN.getStatusCode()); + } + + // 2 files: [ log1, log2 ] in root log directory + FileUtils.writeStringToFile(new File(logRootDir, "log2"), "mylog2", Charset.defaultCharset()); + assertEquals(loggerFileServer.getAllPaths().size(), 2); + assertNotNull(loggerFileServer.downloadLogFile("log1")); + assertNotNull(loggerFileServer.downloadLogFile("log2")); + try { + loggerFileServer.downloadLogFile("log3"); + Assert.fail("Shouldn't reach here"); + } catch (WebApplicationException e1) { + assertEquals(e1.getResponse().getStatus(), Response.Status.FORBIDDEN.getStatusCode()); + } + } finally { + FileUtils.deleteQuietly(logRootDir); + } + } +} diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java index 9f4a35eb3c..9ba042bbcb 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java @@ -59,6 +59,7 @@ import org.apache.pinot.common.metrics.ValidationMetrics; import org.apache.pinot.common.minion.InMemoryTaskManagerStatusCache; import org.apache.pinot.common.minion.TaskGeneratorMostRecentRunInfo; import org.apache.pinot.common.minion.TaskManagerStatusCache; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.ServiceStartableUtils; import org.apache.pinot.common.utils.ServiceStatus; import org.apache.pinot.common.utils.TlsUtils; @@ -472,6 +473,10 @@ public abstract class BaseControllerStarter implements ServiceStartable { bind(_periodicTaskScheduler).to(PeriodicTaskScheduler.class); bind(_sqlQueryExecutor).to(SqlQueryExecutor.class); bind(_pinotLLCRealtimeSegmentManager).to(PinotLLCRealtimeSegmentManager.class); + String loggerRootDir = _config.getProperty(CommonConstants.Controller.CONFIG_OF_LOGGER_ROOT_DIR); + if (loggerRootDir != null) { + bind(new LoggerFileServer(loggerRootDir)).to(LoggerFileServer.class); + } } }); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java index 5000ba4fe5..445d3a1283 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerLogger.java @@ -25,8 +25,16 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -34,10 +42,24 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpVersion; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.pinot.common.utils.FileUploadDownloadClient; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.LoggerUtils; +import org.apache.pinot.common.utils.SimpleHttpResponse; +import org.apache.pinot.common.utils.config.InstanceUtils; +import org.apache.pinot.controller.api.access.AccessType; +import org.apache.pinot.controller.api.access.Authenticate; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -51,6 +73,14 @@ import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_K @Path("/") public class PinotControllerLogger { + private final FileUploadDownloadClient _fileUploadDownloadClient = new FileUploadDownloadClient(); + + @Inject + private LoggerFileServer _loggerFileServer; + + @Inject + PinotHelixResourceManager _pinotHelixResourceManager; + @GET @Path("/loggers") @Produces(MediaType.APPLICATION_JSON) @@ -80,4 +110,112 @@ public class PinotControllerLogger { @ApiParam(value = "Logger level") @QueryParam("level") String level) { return LoggerUtils.setLoggerLevel(loggerName, level); } + + @GET + @Path("/loggers/files") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get all local log files") + public Set<String> getLocalLogFiles() { + try { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.getAllPaths(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Path("/loggers/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Authenticate(AccessType.DELETE) + @ApiOperation(value = "Download a log file") + public Response downloadLogFile( + @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath) { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory is not configured", + Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.downloadLogFile(filePath); + } + + @GET + @Path("/loggers/instances") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Collect log files from all the instances") + public Map<String, Set<String>> getLogFilesFromAllInstances() { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); + } + Map<String, Set<String>> instancesToLogFilesMap = new HashMap<>(); + List<String> onlineInstanceList = _pinotHelixResourceManager.getOnlineInstanceList(); + onlineInstanceList.forEach( + instance -> { + try { + instancesToLogFilesMap.put(instance, getLogFilesFromInstance(instance)); + } catch (Exception e) { + // Skip the instance for any exception. + } + }); + return instancesToLogFilesMap; + } + + @GET + @Path("/loggers/instances/{instanceName}") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Collect log files from a given instance") + public Set<String> getLogFilesFromInstance( + @ApiParam(value = "Instance Name", required = true) @PathParam("instanceName") String instanceName) { + try { + URI uri = new URI(getInstanceBaseUri(instanceName) + "/loggers/files"); + SimpleHttpResponse simpleHttpResponse = _fileUploadDownloadClient.getHttpClient().sendGetRequest(uri); + if (simpleHttpResponse.getStatusCode() >= 400) { + throw new WebApplicationException("Failed to fetch logs from instance name: " + instanceName, + Response.Status.fromStatusCode(simpleHttpResponse.getStatusCode())); + } + String responseString = simpleHttpResponse.getResponse(); + responseString = responseString.substring(1, responseString.length() - 1).replace("\"", ""); + return new HashSet<>(Arrays.asList(responseString.split(","))); + } catch (IOException | URISyntaxException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Path("/loggers/instances/{instanceName}/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Authenticate(AccessType.DELETE) + @ApiOperation(value = "Download a log file from a given instance") + public Response downloadLogFileFromInstance( + @ApiParam(value = "Instance Name", required = true) @PathParam("instanceName") String instanceName, + @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath, + @Context Map<String, String> headers) { + try { + URI uri = UriBuilder.fromUri(getInstanceBaseUri(instanceName)).path("/loggers/download") + .queryParam("filePath", filePath).build(); + RequestBuilder requestBuilder = RequestBuilder.get(uri).setVersion(HttpVersion.HTTP_1_1); + if (MapUtils.isNotEmpty(headers)) { + for (Map.Entry<String, String> header : headers.entrySet()) { + requestBuilder.addHeader(header.getKey(), header.getValue()); + } + } + CloseableHttpResponse httpResponse = _fileUploadDownloadClient.getHttpClient().execute(requestBuilder.build()); + if (httpResponse.getStatusLine().getStatusCode() >= 400) { + throw new WebApplicationException(IOUtils.toString(httpResponse.getEntity().getContent(), "UTF-8"), + Response.Status.fromStatusCode(httpResponse.getStatusLine().getStatusCode())); + } + Response.ResponseBuilder builder = Response.ok(); + builder.entity(httpResponse.getEntity().getContent()); + builder.contentLocation(uri); + builder.header(HttpHeaders.CONTENT_LENGTH, httpResponse.getEntity().getContentLength()); + return builder.build(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + private String getInstanceBaseUri(String instanceName) { + return InstanceUtils.getInstanceBaseUri(_pinotHelixResourceManager.getHelixInstanceConfig(instanceName)); + } } diff --git a/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java b/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java index 97eae3ff7d..dfcef6d6ca 100644 --- a/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java +++ b/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.List; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.util.ListenerConfigUtil; import org.apache.pinot.spi.env.PinotConfiguration; @@ -59,6 +60,10 @@ public class MinionAdminApiApplication extends ResourceConfig { protected void configure() { // TODO: Add bindings as needed in future. bind(instanceId).named(MINION_INSTANCE_ID); + String loggerRootDir = minionConf.getProperty(CommonConstants.Minion.CONFIG_OF_LOGGER_ROOT_DIR); + if (loggerRootDir != null) { + bind(new LoggerFileServer(loggerRootDir)).to(LoggerFileServer.class); + } } }); diff --git a/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotMinionLogger.java b/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotMinionLogger.java index 7572cd9cc5..fb0d282fba 100644 --- a/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotMinionLogger.java +++ b/pinot-minion/src/main/java/org/apache/pinot/minion/api/resources/PinotMinionLogger.java @@ -25,8 +25,11 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -37,6 +40,7 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.LoggerUtils; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -51,6 +55,9 @@ import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_K @Path("/") public class PinotMinionLogger { + @Inject + private LoggerFileServer _loggerFileServer; + @GET @Path("/loggers") @Produces(MediaType.APPLICATION_JSON) @@ -80,4 +87,32 @@ public class PinotMinionLogger { @ApiParam(value = "Logger level") @QueryParam("level") String level) { return LoggerUtils.setLoggerLevel(loggerName, level); } + + @GET + @Path("/loggers/files") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get all local log files") + public Set<String> getLocalLogFiles() { + try { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.getAllPaths(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Path("/loggers/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @ApiOperation(value = "Download a log file") + public Response downloadLogFile( + @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath) { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory is not configured", + Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.downloadLogFile(filePath); + } } diff --git a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/PinotServerLogger.java b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/PinotServerLogger.java index 0e69d89539..14b7d71d61 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/PinotServerLogger.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/PinotServerLogger.java @@ -25,8 +25,11 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -37,6 +40,7 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.LoggerUtils; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -51,6 +55,9 @@ import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_K @Path("/") public class PinotServerLogger { + @Inject + private LoggerFileServer _loggerFileServer; + @GET @Path("/loggers") @Produces(MediaType.APPLICATION_JSON) @@ -80,4 +87,32 @@ public class PinotServerLogger { @ApiParam(value = "Logger level") @QueryParam("level") String level) { return LoggerUtils.setLoggerLevel(loggerName, level); } + + @GET + @Path("/loggers/files") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Get all local log files") + public Set<String> getLocalLogFiles() { + try { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.getAllPaths(); + } catch (IOException e) { + throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @GET + @Path("/loggers/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @ApiOperation(value = "Download a log file") + public Response downloadLogFile( + @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath) { + if (_loggerFileServer == null) { + throw new WebApplicationException("Root log directory is not configured", + Response.Status.INTERNAL_SERVER_ERROR); + } + return _loggerFileServer.downloadLogFile(filePath); + } } diff --git a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java index 48c07aeea1..c0dc370cf9 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java @@ -29,6 +29,7 @@ import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.util.ListenerConfigUtil; import org.apache.pinot.server.access.AccessControlFactory; @@ -70,6 +71,10 @@ public class AdminApiApplication extends ResourceConfig { bind(_serverInstance.getServerMetrics()).to(ServerMetrics.class); bind(accessControlFactory).to(AccessControlFactory.class); bind(serverConf.getProperty(CommonConstants.Server.CONFIG_OF_INSTANCE_ID)).named(SERVER_INSTANCE_ID); + String loggerRootDir = serverConf.getProperty(CommonConstants.Server.CONFIG_OF_LOGGER_ROOT_DIR); + if (loggerRootDir != null) { + bind(new LoggerFileServer(loggerRootDir)).to(LoggerFileServer.class); + } } }); diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java index d437b76ea7..738800c6cb 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java @@ -262,6 +262,8 @@ public class CommonConstants { // TODO: Support populating clientIp for GrpcRequestIdentity. public static final boolean DEFAULT_BROKER_REQUEST_CLIENT_IP_LOGGING = false; + public static final String CONFIG_OF_LOGGER_ROOT_DIR = "pinot.broker.logger.root.dir"; + public static class Request { public static final String SQL = "sql"; public static final String TRACE = "trace"; @@ -453,6 +455,7 @@ public class CommonConstants { // The complete config key is pinot.server.instance.segment.store.uri public static final String CONFIG_OF_SEGMENT_STORE_URI = "segment.store.uri"; + public static final String CONFIG_OF_LOGGER_ROOT_DIR = "pinot.server.logger.root.dir"; public static class SegmentCompletionProtocol { public static final String PREFIX_OF_CONFIG_OF_SEGMENT_UPLOADER = "pinot.server.segment.uploader"; @@ -532,6 +535,7 @@ public class CommonConstants { "pinot.controller.query.rewriter.class.names"; //Set to true to load all services tagged and compiled with hk2-metadata-generator. Default to False public static final String CONTROLLER_SERVICE_AUTO_DISCOVERY = "pinot.controller.service.auto.discovery"; + public static final String CONFIG_OF_LOGGER_ROOT_DIR = "pinot.controller.logger.root.dir"; } public static class Minion { @@ -573,6 +577,7 @@ public class CommonConstants { public static final String CONFIG_TASK_AUTH_NAMESPACE = "task.auth"; public static final String MINION_TLS_PREFIX = "pinot.minion.tls"; public static final String CONFIG_OF_MINION_QUERY_REWRITER_CLASS_NAMES = "pinot.minion.query.rewriter.class.names"; + public static final String CONFIG_OF_LOGGER_ROOT_DIR = "pinot.minion.logger.root.dir"; } public static class ControllerJob { --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org