Skip to content

Commit 3ad3c35

Browse files
committed
Various updates
1 parent 45843d6 commit 3ad3c35

38 files changed

Lines changed: 3797 additions & 499 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ Thumbs.db
5050
coverage/
5151
/exec/java-exec/src/main/resources/webapp/dist/
5252
/exec/java-exec/src/main/resources/webapp/node/
53+
result-cache/

contrib/storage-splunk/src/main/java/org/apache/drill/exec/store/splunk/SplunkConnection.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,19 @@ public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
152152
// Fall back to the Splunk SDK default if our value is null by not setting
153153
loginArgs.setPort(port);
154154
}
155-
loginArgs.setPassword(credentials.map(UsernamePasswordCredentials::getPassword).orElse(null));
156-
loginArgs.setUsername(credentials.map(UsernamePasswordCredentials::getUsername).orElse(null));
155+
156+
// If the user provides a token, do not set the username/password
157+
if (token != null && !token.isEmpty()) {
158+
loginArgs.setToken("Bearer " + token);
159+
loginArgs.setPassword(credentials.map(UsernamePasswordCredentials::getPassword).orElse(null));
160+
loginArgs.setUsername(credentials.map(UsernamePasswordCredentials::getUsername).orElse(null));
161+
} else {
162+
loginArgs.setPassword(credentials.map(UsernamePasswordCredentials::getPassword).orElse(null));
163+
loginArgs.setUsername(credentials.map(UsernamePasswordCredentials::getUsername).orElse(null));
164+
}
165+
157166
loginArgs.setApp(app);
158167
loginArgs.setOwner(owner);
159-
loginArgs.setToken(token);
160168
loginArgs.setCookie(cookie);
161169

162170
try {

exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public DrillRestServer(final WorkManager workManager, final ServletContext servl
112112
register(SmtpConfigResources.class);
113113
register(NotebookResources.class);
114114
register(ResultCacheResources.class);
115+
register(ProfileConfigResources.class);
115116

116117
logger.info("Registered {} resource classes", 23);
117118

exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogsResources.java

Lines changed: 196 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222
import com.google.common.base.Preconditions;
2323
import com.google.common.collect.Sets;
2424
import org.apache.drill.common.exceptions.DrillRuntimeException;
25+
import org.apache.drill.common.logical.FormatPluginConfig;
26+
import org.apache.drill.common.logical.StoragePluginConfig;
2527
import org.apache.drill.exec.ExecConstants;
28+
import org.apache.drill.exec.store.StoragePluginRegistry;
29+
import org.apache.drill.exec.store.dfs.FileSystemConfig;
30+
import org.apache.drill.exec.store.dfs.WorkspaceConfig;
2631
import org.apache.drill.exec.work.WorkManager;
27-
import org.glassfish.jersey.server.mvc.Viewable;
2832
import org.joda.time.DateTime;
2933
import org.joda.time.format.DateTimeFormat;
3034
import org.joda.time.format.DateTimeFormatter;
@@ -34,13 +38,13 @@
3438
import jakarta.annotation.security.RolesAllowed;
3539
import jakarta.inject.Inject;
3640
import jakarta.ws.rs.GET;
41+
import jakarta.ws.rs.POST;
3742
import jakarta.ws.rs.Path;
3843
import jakarta.ws.rs.PathParam;
3944
import jakarta.ws.rs.Produces;
4045
import jakarta.ws.rs.core.HttpHeaders;
4146
import jakarta.ws.rs.core.MediaType;
4247
import jakarta.ws.rs.core.Response;
43-
import jakarta.ws.rs.core.SecurityContext;
4448
import jakarta.xml.bind.annotation.XmlRootElement;
4549

4650
import java.io.BufferedReader;
@@ -60,8 +64,16 @@
6064
public class LogsResources {
6165
private static final Logger logger = LoggerFactory.getLogger(LogsResources.class);
6266

63-
@Inject DrillRestServer.UserAuthEnabled authEnabled;
64-
@Inject SecurityContext sc;
67+
private static final String DFS_PLUGIN_NAME = "dfs";
68+
private static final String LOGS_WORKSPACE_NAME = "logs";
69+
private static final String DRILL_LOG_FORMAT_NAME = "drilllog";
70+
71+
// Regex to parse Drill's default logback format:
72+
// %date{ISO8601} [%thread] %-5level %logger{36} - %msg%n
73+
// Example: 2025-03-12T10:30:45,123 [main] INFO o.a.d.e.s.DrillbitContext - Starting
74+
private static final String DRILL_LOG_REGEX =
75+
"(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2},\\d+)\\s+\\[([^\\]]+)\\]\\s+(\\w+)\\s+([^\\s]+)\\s+-\\s+(.*)";
76+
6577
@Inject WorkManager work;
6678

6779
private static final FileFilter file_filter = new FileFilter() {
@@ -76,43 +88,55 @@ public boolean accept(File file) {
7688
@GET
7789
@Path("/logs")
7890
@Produces(MediaType.TEXT_HTML)
79-
public Viewable getLogs() {
80-
Set<Log> logs = getLogsJSON();
81-
return ViewableWithPermissions.create(authEnabled.get(), "/rest/logs/list.ftl", sc, logs);
91+
public Response getLogs() {
92+
return Response.seeOther(java.net.URI.create("/sqllab#/logs")).build();
8293
}
8394

8495
@GET
8596
@Path("/logs.json")
8697
@Produces(MediaType.APPLICATION_JSON)
87-
public Set<Log> getLogsJSON() {
98+
public Response getLogsJSON() {
8899
Set<Log> logs = Sets.newTreeSet();
89-
File[] files = getLogFolder().listFiles(file_filter);
90100

91-
for (File file : files) {
92-
logs.add(new Log(file.getName(), file.length(), file.lastModified()));
101+
String logDir = System.getenv("DRILL_LOG_DIR");
102+
if (logDir == null) {
103+
return Response.ok(logs).build();
104+
}
105+
106+
File folder = new File(logDir);
107+
if (!folder.isDirectory()) {
108+
logger.warn("DRILL_LOG_DIR does not point to a valid directory: {}", logDir);
109+
return Response.ok(logs).build();
110+
}
111+
112+
File[] files = folder.listFiles(file_filter);
113+
if (files != null) {
114+
for (File file : files) {
115+
logs.add(new Log(file.getName(), file.length(), file.lastModified()));
116+
}
93117
}
94118

95-
return logs;
119+
return Response.ok(logs).build();
96120
}
97121

98122
@GET
99123
@Path("/log/{name}/content")
100124
@Produces(MediaType.TEXT_HTML)
101-
public Viewable getLog(@PathParam("name") String name) throws IOException {
102-
try {
103-
LogContent content = getLogJSON(name);
104-
return ViewableWithPermissions.create(authEnabled.get(), "/rest/logs/log.ftl", sc, content);
105-
} catch (Exception | Error e) {
106-
logger.error("Exception was thrown when fetching log {} :\n{}", name, e);
107-
return ViewableWithPermissions.create(authEnabled.get(), "/rest/errorMessage.ftl", sc, e);
108-
}
125+
public Response getLog(@PathParam("name") String name) {
126+
return Response.seeOther(java.net.URI.create("/sqllab#/logs")).build();
109127
}
110128

111129
@GET
112130
@Path("/log/{name}/content.json")
113131
@Produces(MediaType.APPLICATION_JSON)
114-
public LogContent getLogJSON(@PathParam("name") final String name) throws IOException {
115-
File file = getFileByName(getLogFolder(), name);
132+
public Response getLogJSON(@PathParam("name") final String name) throws IOException {
133+
File folder = getLogFolderSafe();
134+
if (folder == null) {
135+
return Response.status(Response.Status.SERVICE_UNAVAILABLE)
136+
.entity(new LogSetupResponse(false, "DRILL_LOG_DIR is not configured"))
137+
.build();
138+
}
139+
File file = getFileByName(folder, name);
116140

117141
final int maxLines = work.getContext().getOptionManager().getOption(ExecConstants.WEB_LOGS_MAX_LINES).num_val.intValue();
118142

@@ -131,27 +155,174 @@ protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
131155
cache.put(i++, line);
132156
}
133157

134-
return new LogContent(file.getName(), cache.values(), maxLines);
158+
return Response.ok(new LogContent(file.getName(), cache.values(), maxLines)).build();
135159
}
136160
}
137161

138162
@GET
139163
@Path("/log/{name}/download")
140164
@Produces(MediaType.TEXT_PLAIN)
141165
public Response getFullLog(@PathParam("name") final String name) {
142-
File file = getFileByName(getLogFolder(), name);
166+
File folder = getLogFolderSafe();
167+
if (folder == null) {
168+
return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
169+
}
170+
File file = getFileByName(folder, name);
143171
return Response.ok(file)
144172
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", name))
145173
.build();
146174
}
147175

176+
@GET
177+
@Path("/api/v1/logs/sql-status")
178+
@Produces(MediaType.APPLICATION_JSON)
179+
public Response getLogsSqlStatus() {
180+
Map<String, Object> status = new LinkedHashMap<>();
181+
182+
// Check if DRILL_LOG_DIR is set
183+
String logDir = System.getenv("DRILL_LOG_DIR");
184+
status.put("logDirConfigured", logDir != null);
185+
status.put("logDir", logDir);
186+
187+
// Check if the dfs.logs workspace exists
188+
boolean workspaceExists = false;
189+
boolean formatExists = false;
190+
try {
191+
StoragePluginRegistry storage = work.getContext().getStorage();
192+
StoragePluginConfig config = storage.getStoredConfig(DFS_PLUGIN_NAME);
193+
if (config instanceof FileSystemConfig) {
194+
FileSystemConfig fsConfig = (FileSystemConfig) config;
195+
workspaceExists = fsConfig.getWorkspaces().containsKey(LOGS_WORKSPACE_NAME);
196+
formatExists = fsConfig.getFormats().containsKey(DRILL_LOG_FORMAT_NAME);
197+
}
198+
} catch (Exception e) {
199+
logger.debug("Error checking logs SQL status", e);
200+
}
201+
202+
status.put("workspaceExists", workspaceExists);
203+
status.put("formatExists", formatExists);
204+
status.put("ready", workspaceExists && formatExists);
205+
206+
return Response.ok(status).build();
207+
}
208+
209+
@POST
210+
@Path("/api/v1/logs/sql-setup")
211+
@Produces(MediaType.APPLICATION_JSON)
212+
public Response setupLogsSql() {
213+
String logDir = System.getenv("DRILL_LOG_DIR");
214+
if (logDir == null) {
215+
return Response.status(Response.Status.BAD_REQUEST)
216+
.entity(new LogSetupResponse(false,
217+
"DRILL_LOG_DIR environment variable is not set"))
218+
.build();
219+
}
220+
221+
try {
222+
StoragePluginRegistry storage = work.getContext().getStorage();
223+
StoragePluginConfig config = storage.getStoredConfig(DFS_PLUGIN_NAME);
224+
225+
if (!(config instanceof FileSystemConfig)) {
226+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
227+
.entity(new LogSetupResponse(false,
228+
"dfs plugin is not a file system plugin"))
229+
.build();
230+
}
231+
232+
FileSystemConfig fsConfig = (FileSystemConfig) config;
233+
boolean modified = false;
234+
235+
// Add the logs workspace if it doesn't exist
236+
if (!fsConfig.getWorkspaces().containsKey(LOGS_WORKSPACE_NAME)) {
237+
FileSystemConfig copy = fsConfig.copy();
238+
copy.getWorkspaces().put(LOGS_WORKSPACE_NAME,
239+
new WorkspaceConfig(logDir, false, DRILL_LOG_FORMAT_NAME, false));
240+
storage.put(DFS_PLUGIN_NAME, copy);
241+
modified = true;
242+
logger.info("Created dfs.logs workspace pointing to {}", logDir);
243+
}
244+
245+
// Add the drilllog format if it doesn't exist
246+
StoragePluginConfig updatedConfig = storage.getStoredConfig(DFS_PLUGIN_NAME);
247+
if (updatedConfig instanceof FileSystemConfig) {
248+
FileSystemConfig updatedFsConfig = (FileSystemConfig) updatedConfig;
249+
if (!updatedFsConfig.getFormats().containsKey(DRILL_LOG_FORMAT_NAME)) {
250+
// Build the format config as JSON to avoid a compile-time dependency
251+
// on the contrib/format-log module
252+
String formatJson = "{"
253+
+ "\"type\": \"logRegex\","
254+
+ "\"regex\": \"" + DRILL_LOG_REGEX.replace("\\", "\\\\") + "\","
255+
+ "\"extension\": \"log\","
256+
+ "\"maxErrors\": 10,"
257+
+ "\"schema\": ["
258+
+ " {\"fieldName\": \"log_timestamp\", \"fieldType\": \"VARCHAR\"},"
259+
+ " {\"fieldName\": \"thread\", \"fieldType\": \"VARCHAR\"},"
260+
+ " {\"fieldName\": \"level\", \"fieldType\": \"VARCHAR\"},"
261+
+ " {\"fieldName\": \"logger\", \"fieldType\": \"VARCHAR\"},"
262+
+ " {\"fieldName\": \"message\", \"fieldType\": \"VARCHAR\"}"
263+
+ "]}";
264+
265+
FormatPluginConfig logFormat = storage.mapper()
266+
.readValue(formatJson, FormatPluginConfig.class);
267+
storage.putFormatPlugin(DFS_PLUGIN_NAME, DRILL_LOG_FORMAT_NAME, logFormat);
268+
modified = true;
269+
logger.info("Created drilllog format plugin for parsing Drill logs");
270+
}
271+
}
272+
273+
String msg = modified
274+
? "Log SQL workspace and format configured successfully"
275+
: "Log SQL workspace and format were already configured";
276+
return Response.ok(new LogSetupResponse(true, msg)).build();
277+
278+
} catch (Exception e) {
279+
logger.error("Error setting up logs SQL workspace", e);
280+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
281+
.entity(new LogSetupResponse(false,
282+
"Failed to configure: " + e.getMessage()))
283+
.build();
284+
}
285+
}
286+
287+
public static class LogSetupResponse {
288+
@JsonProperty
289+
public final boolean success;
290+
@JsonProperty
291+
public final String message;
292+
293+
public LogSetupResponse(boolean success, String message) {
294+
this.success = success;
295+
this.message = message;
296+
}
297+
}
298+
148299
private File getLogFolder() {
149300
return new File(Preconditions.checkNotNull(System.getenv("DRILL_LOG_DIR"), "DRILL_LOG_DIR variable is not set"));
150301
}
151302

303+
/**
304+
* Returns the log folder if DRILL_LOG_DIR is set and valid, or null otherwise.
305+
*/
306+
private File getLogFolderSafe() {
307+
String logDir = System.getenv("DRILL_LOG_DIR");
308+
if (logDir == null) {
309+
return null;
310+
}
311+
File folder = new File(logDir);
312+
if (!folder.isDirectory()) {
313+
return null;
314+
}
315+
return folder;
316+
}
317+
152318
private File getFileByName(File folder, final String name) {
319+
// Prevent path traversal attacks
320+
if (name.contains("..") || name.contains("/") || name.contains("\\")) {
321+
throw new DrillRuntimeException("Invalid log file name: " + name);
322+
}
323+
153324
File[] files = folder.listFiles((dir, fileName) -> fileName.equals(name));
154-
if (files.length == 0) {
325+
if (files == null || files.length == 0) {
155326
throw new DrillRuntimeException(name + " doesn't exist");
156327
}
157328
return files[0];

0 commit comments

Comments
 (0)