Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul of SlaveLaunchLogs #517

Merged
merged 25 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8095453
agent-logs sketch
jglick Feb 29, 2024
ac39d18
`SlaveLaunchLogsTest.onlineInboundAgent`
jglick Feb 29, 2024
a70c20d
`SlaveLaunchLogsTest.offlineAgent`
jglick Feb 29, 2024
98c04e5
Javadoc
jglick Feb 29, 2024
0cd0f81
Merge branch 'master' of https://github.com/jenkinsci/support-core-pl…
jglick Mar 1, 2024
3d0a97a
Exploring `SlaveLaunchLogs` behavior
jglick Mar 1, 2024
cdf931f
More `SlaveLaunchLogsTest`
jglick Mar 1, 2024
0b192d3
`SlaveLaunchLogsTest.passwords`
jglick Mar 1, 2024
e28c8c3
Worked out a better `SlaveLaunchLogs`, but depends on patch to `Slave…
jglick Mar 1, 2024
83ed22f
Need to flush logs also for `deletedAgent`
jglick Mar 1, 2024
4a2adc4
Reverting changes extracted to #518
jglick Mar 1, 2024
154adca
Setting a timestamp, switching category
jglick Mar 1, 2024
92cda57
RC
jglick Mar 4, 2024
d698388
No need to assert that `SupportTestUtils.invokeComponentToString` is …
jglick Mar 4, 2024
93f8c92
`Security2186Test` failure caused by renamed bundle entry
jglick Mar 4, 2024
98ee7ab
File handle leak caught by Windows tests
jglick Mar 4, 2024
6fd40f8
Merge branch 'master' of https://github.com/jenkinsci/support-core-pl…
jglick Mar 4, 2024
72beede
Better handling of rotated logs
jglick Mar 4, 2024
d072846
More robust way to wait for `Connection terminated` message
jglick Mar 4, 2024
9169e04
`SlaveLaunchLogsTest.offlineAgent` still flaky on Windows
jglick Mar 4, 2024
2fa5cb1
https://github.com/jenkinsci/jenkins/pull/9009 released
jglick Mar 5, 2024
a573c50
Merge branch 'master' of https://github.com/jenkinsci/support-core-pl…
jglick Mar 18, 2024
fc62f58
Working around lack of JENKINS-72799 to avoid requiring a weekly core
jglick Mar 18, 2024
c9d6e95
Merge branch 'master' into agent-logs
Dohbedoh Mar 18, 2024
5a924b4
SpotBugs
jglick Mar 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@

<properties>
<changelist>999999-SNAPSHOT</changelist>
<jenkins.version>2.361.4</jenkins.version>
<!-- TODO https://github.com/jenkinsci/jenkins/pull/9009 -->
<jenkins.version>2.448-rc34683.5cd5ff72b_b_89</jenkins.version>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<spotless.check.skip>false</spotless.check.skip>
</properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cloudbees.jenkins.support.api;

import com.cloudbees.jenkins.support.filter.PasswordRedactor;
import com.cloudbees.jenkins.support.impl.SlaveLaunchLogs;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -11,6 +12,9 @@
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;

/**
* @see SlaveLaunchLogs
*/
public class LaunchLogsFileContent extends FileContent {

public LaunchLogsFileContent(String name, String[] filterableParameters, File file, long maxSize) {
Expand Down
169 changes: 67 additions & 102 deletions src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,28 @@
import com.cloudbees.jenkins.support.timer.FileListCapComponent;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.console.ConsoleLogFilter;
import hudson.console.LineTransformationOutputStream;
import hudson.model.AbstractModelObject;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Run;
import hudson.security.Permission;
import hudson.slaves.Cloud;
import hudson.slaves.SlaveComputer;
import hudson.triggers.SafeTimerTask;
import hudson.util.io.RewindableRotatingFileOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import jenkins.model.Jenkins;
import org.apache.commons.io.output.TeeOutputStream;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

Expand All @@ -54,6 +64,9 @@
@Extension
public class SlaveLaunchLogs extends ObjectComponent<Computer> {

private static final int MAX_ROTATE_LOGS =
Integer.getInteger(SlaveLaunchLogs.class.getName() + ".MAX_ROTATE_LOGS", 9);

@DataBoundConstructor
public SlaveLaunchLogs() {
super();
Expand All @@ -73,128 +86,80 @@

@Override
public void addContents(@NonNull Container container) {
addAgentsLaunchLogs(container);
File log = ExtensionList.lookupSingleton(LogArchiver.class).log;
if (log.isFile()) {
// TODO perhaps include rotated files as well

Check warning on line 91 in src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: perhaps include rotated files as well
container.add(new LaunchLogsFileContent(
"nodes/slave/launchLogs.log", new String[] {}, log, FileListCapComponent.MAX_FILE_SIZE));
}
}

@NonNull
@Override
public ComponentCategory getCategory() {
return ComponentCategory.AGENT; // TODO or LOGS?
return ComponentCategory.LOGS;
}

@Override
public void addContents(@NonNull Container container, Computer item) {
if (item.getNode() == null) {
return;
}
File lastLog = item.getLogFile();
if (lastLog.exists()) {
Agent agent = new Agent(new File(Jenkins.get().getRootDir(), "logs/slaves/" + item.getName()), lastLog);
if (!agent.isTooOld()) {
addAgentLaunchLogs(container, agent);
}
}
}

/**
* <p>
* In the presence of {@link Cloud} plugins like EC2, we want to find past agents, not just current ones.
* So we don't try to loop through {@link Node} here but just try to look at the file systems to find them
* all.
*
* <p>
* Generally these cloud plugins do not clean up old logs, so if run for a long time, the log directory
* will be full of old files that are not very interesting. Use some heuristics to cut off logs
* that are old.
*/
private void addAgentsLaunchLogs(Container result) {

List<Agent> all = new ArrayList<>();

{ // find all the agent launch log files and sort them newer ones first
File agentLogsDir = new File(Jenkins.get().getRootDir(), "logs/slaves");
File[] logs = agentLogsDir.listFiles();
if (logs != null) {
for (File dir : logs) {
File lastLog = new File(dir, "slave.log");
if (lastLog.exists()) {
Agent s = new Agent(dir, lastLog);
if (s.isTooOld()) continue; // we don't care
all.add(s);
}
if (item.getNode() != null

Check warning on line 105 in src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 105 is only partially covered, one branch is missing
&& item.getLogFile().lastModified() >= System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)) {

Check warning on line 106 in src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 106 is only partially covered, one branch is missing
File dir = new File(Jenkins.get().getRootDir(), "logs/slaves/" + item.getName());
File[] files = dir.listFiles(ROTATED_LOGFILE_FILTER);
if (files != null) {
for (File f : files) {
container.add(new LaunchLogsFileContent(
"nodes/slave/{0}/launchLogs/{1}",
new String[] {dir.getName(), f.getName()}, f, FileListCapComponent.MAX_FILE_SIZE));
}
}

Collections.sort(all);
}
{ // this might be still too many, so try to cap them.
int acceptableSize = Math.max(256, Jenkins.get().getNodes().size() * 5);

if (all.size() > acceptableSize) all = all.subList(0, acceptableSize);
}

// now add them all
all.forEach(it -> addAgentLaunchLogs(result, it));
}

private void addAgentLaunchLogs(Container container, Agent agent) {
File[] files = agent.dir.listFiles(ROTATED_LOGFILE_FILTER);
if (files != null) {
for (File f : files) {
container.add(new LaunchLogsFileContent(
"nodes/slave/{0}/launchLogs/{1}",
new String[] {agent.getName(), f.getName()}, f, FileListCapComponent.MAX_FILE_SIZE));
}
}
}

static class Agent implements Comparable<Agent> {
/**
* Launch log directory of the agent: logs/slaves/NAME
*/
final File dir;

final long time;

Agent(File dir, File lastLog) {
this.dir = dir;
this.time = lastLog.lastModified();
}
@Extension
public static final class LogArchiver extends ConsoleLogFilter {

/** Agent name */
String getName() {
return dir.getName();
}
final File log = new File(SafeTimerTask.getLogsRoot(), "nodeLaunchLogs.txt");
private final RewindableRotatingFileOutputStream stream =
new RewindableRotatingFileOutputStream(log, MAX_ROTATE_LOGS);

/**
* Use the primary log file's timestamp to compare newer agents from older agents.
*
* sort in descending order; newer ones first.
*/
public int compareTo(Agent that) {
return Long.compare(that.time, this.time);
@Override
public OutputStream decorateLogger(Computer computer, OutputStream logger) {
if (computer instanceof SlaveComputer) {

Check warning on line 128 in src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 128 is only partially covered, one branch is missing
return new TeeOutputStream(logger, new PrefixedStream(stream, computer.getName()));
} else {
return logger;

Check warning on line 131 in src/main/java/com/cloudbees/jenkins/support/impl/SlaveLaunchLogs.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 131 is not covered by tests
}
}

@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
public OutputStream decorateLogger(Run build, OutputStream logger) throws IOException, InterruptedException {
return logger;
}
}

Agent agent = (Agent) o;
static class PrefixedStream extends LineTransformationOutputStream.Delegating {
private final String name;

return time == agent.time;
PrefixedStream(OutputStream out, String name) {
super(out);
this.name = name;
}

@Override
public int hashCode() {
return (int) (time ^ (time >>> 32));
}

/**
* If the file is more than 7 days old, it is considered too old.
*/
public boolean isTooOld() {
return time < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
protected void eol(byte[] b, int len) throws IOException {
synchronized (out) {
out.write('[');
out.write(DateTimeFormatter.ISO_INSTANT
.format(Instant.now().truncatedTo(ChronoUnit.MILLIS))
Comment on lines +235 to +236
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.getBytes(StandardCharsets.US_ASCII));
out.write(' ');
out.write(name.getBytes(StandardCharsets.UTF_8));
out.write(']');
out.write(' ');
out.write(b, 0, len);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ public void secretsFilterWhenSystemPropertyContainsPasswordThenValueRedacted() t
verifyFileIfContainsPassword(zip, "nodes/master/system.properties", "test2186.trustStorePassword");
verifyFileIfContainsPassword(zip, "nodes/master/environment.txt", "test2186.trustStorePassword");
verifyFileIfContainsPassword(zip, "nodes/slave/slave0/config.xml", "-Dtest2186.trustStoreAgentPassword");
verifyFileIfContainsPassword(
zip, "nodes/slave/slave0/launchLogs/slave.log", "-Dtest2186.trustStoreAgentPassword");
verifyFileIfContainsPassword(zip, "nodes/slave/launchLogs.log", "-Dtest2186.trustStoreAgentPassword");
}

private void verifyFileIfContainsPassword(ZipFile zip, String fileName, String expectedPasswordKey)
Expand Down
Loading
Loading