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

Updated logrotate frequency to allow for an override #1814

Merged
merged 26 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
758e89b
Updated logrotate frequency to allow for an override by listing addit…
kdorosh Jun 27, 2018
4623ebd
Fix checkstyle on .* import for mockito.
kdorosh Jun 27, 2018
e50f527
Update POJOs to use Optional the way it's supposed to be used.
kdorosh Jun 28, 2018
25be9f8
Really need to figure out how to stop IntelliJ from doing this.
kdorosh Jun 28, 2018
025c732
Update to deserialize to Guava Optionals.
kdorosh Jun 28, 2018
8cbb242
Fix handlebars template apply error when logrotateFrequencyOverride i…
kdorosh Jun 29, 2018
2552650
Very silly error wrapping an extra Optional and fixing whitespace.
kdorosh Jun 29, 2018
c94a606
Jackson can deserialize Optionals to Strings, so put that back.
kdorosh Jul 2, 2018
152eb8c
Make parameters final that can be made final.
kdorosh Jul 2, 2018
bcc0379
Updated logrotate to support hourly rotates via separate config direc…
kdorosh Jul 3, 2018
2631ba5
Consistent spacing.
kdorosh Jul 3, 2018
5092d6b
Bug in getAllExtraFiles and some nitpicking formatting.
kdorosh Jul 3, 2018
10b1b98
Add forgotten @Provides annotation for new logrotate template.
kdorosh Jul 3, 2018
76d30af
Fix bug with calling Optional.get on potentially absent global hourly…
kdorosh Jul 3, 2018
1fe3159
Part two to Optional.get on absent global frequency bug.
kdorosh Jul 3, 2018
6a17308
No longer calling Optional.get on a findFirst() that may return absent.
kdorosh Jul 5, 2018
d1255c0
Proper equality comparison.
kdorosh Jul 5, 2018
79f9fd3
Prevent duplicated hourly/non-hourly entries in both configs.
kdorosh Jul 5, 2018
1d293fb
Proper cleanup of second logrotate config.
kdorosh Jul 5, 2018
09de273
Rename methods and replace java.util Optional with Guava Optional.
kdorosh Jul 9, 2018
a8f6a7b
Don't enforce directory present in config, create if doesn't exist
ssalinas Jul 12, 2018
c62b6e0
make these log levels debug instead
ssalinas Jul 17, 2018
b9053f6
check exists on each before delete
ssalinas Jul 17, 2018
7ba27b0
fix this if statement
ssalinas Jul 17, 2018
2873512
Always attempt to remove file if exists
ssalinas Jul 17, 2018
cd13a97
Missing !
ssalinas Jul 17, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

public enum SingularityExecutorLogrotateFrequency {
HOURLY("daily", Optional.of("0 * * * *")), // we have to use the "daily" frequency because not all versions of logrotate support "hourly"
DAILY("daily", Optional.<String>absent()),
WEEKLY("weekly", Optional.<String>absent()),
MONTHLY("monthly", Optional.<String>absent());
DAILY("daily", Optional.absent()),
WEEKLY("weekly", Optional.absent()),
MONTHLY("monthly", Optional.absent());

private final String logrotateValue;
private final Optional<String> cronSchedule;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Optional;
import com.hubspot.singularity.executor.SingularityExecutorLogrotateFrequency;

public class SingularityExecutorLogrotateAdditionalFile {
private final String filename;
private final Optional<String> extension;
private final Optional<String> dateformat;
private Optional<String> extension;
private Optional<String> dateformat;
private Optional<SingularityExecutorLogrotateFrequency> logrotateFrequencyOverride;

@JsonCreator
public static SingularityExecutorLogrotateAdditionalFile fromString(String value) {
return new SingularityExecutorLogrotateAdditionalFile(value, Optional.<String>absent(), Optional.<String>absent());
return new SingularityExecutorLogrotateAdditionalFile(value, null, null, null);
}

@JsonCreator
public SingularityExecutorLogrotateAdditionalFile(@JsonProperty("filename") String filename,
@JsonProperty("extension") Optional<String> extension,
@JsonProperty("dateformat") Optional<String> dateformat) {
@JsonProperty("extension") String extension,
Copy link
Member

Choose a reason for hiding this comment

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

Curious why you removed the optionals from the arguments. Both cases should work for that particular usage but there are a few things to keep in mind:

  • Jackson can handle the optionals just fine when reading from json and a missing key will be an absent optional
  • Not relevant for this case, but always be on the lookout for other uses of a constructor that would cause something like this to be a breaking change. For this particular case, these POJOs are just used to model something read from json. But for other cases, releasing a new version with a changed constructor would break other users

@JsonProperty("dateformat") String dateformat,
@JsonProperty("logrotateFrequencyOverride") SingularityExecutorLogrotateFrequency logrotateFrequencyOverride) {
this.filename = filename;
this.extension = extension;
this.dateformat = dateformat;
this.extension = Optional.fromNullable(extension);
this.dateformat = Optional.fromNullable(dateformat);
this.logrotateFrequencyOverride = Optional.fromNullable(logrotateFrequencyOverride);
}

public String getFilename() {
Expand All @@ -34,4 +38,9 @@ public Optional<String> getExtension() {
public Optional<String> getDateformat() {
return dateformat;
}

public Optional<SingularityExecutorLogrotateFrequency> getLogrotateFrequencyOverride() {
return logrotateFrequencyOverride;
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.hubspot.singularity.executor.models;

import com.google.common.base.Optional;
import com.hubspot.singularity.executor.SingularityExecutorLogrotateFrequency;

public class LogrotateAdditionalFile {
private final String filename;
private final String extension;
private final String dateformat;
private final Optional<SingularityExecutorLogrotateFrequency> logrotateFrequencyOverride;

public LogrotateAdditionalFile(String filename, String extension, String dateformat) {
public LogrotateAdditionalFile(String filename, String extension, String dateformat, Optional<SingularityExecutorLogrotateFrequency> logrotateFrequencyOverride) {
this.filename = filename;
this.extension = extension;
this.dateformat = dateformat;
this.logrotateFrequencyOverride = logrotateFrequencyOverride;
}

public String getFilename() {
Expand All @@ -23,12 +28,18 @@ public String getDateformat() {
return dateformat;
}

public String getLogrotateFrequencyOverride() {
return logrotateFrequencyOverride.isPresent() ?
logrotateFrequencyOverride.get().getLogrotateValue() : "";
}

@Override
public String toString() {
return "LogrotateAdditionalFile{" +
"filename='" + filename + '\'' +
", extension='" + extension + '\'' +
", dateformat='" + dateformat + '\'' +
", frequency='" + logrotateFrequencyOverride + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public List<LogrotateAdditionalFile> getExtrasFiles() {
new LogrotateAdditionalFile(
taskDefinition.getTaskDirectoryPath().resolve(additionalFile.getFilename()).toString(),
additionalFile.getExtension().or(Strings.emptyToNull(Files.getFileExtension(additionalFile.getFilename()))),
dateformat
dateformat,
additionalFile.getLogrotateFrequencyOverride()
));
}

Expand Down
1 change: 1 addition & 0 deletions SingularityExecutor/src/main/resources/logrotate.conf.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ notifempty

{{#if extrasFiles}}
{{#each extrasFiles}}{{{filename}}} {
{{#if logrotateFrequencyOverride }}{{{logrotateFrequencyOverride}}}{{/if}}
Copy link
Member

Choose a reason for hiding this comment

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

We should double check if this will 'just work'. Not all versions of logrotate support hourly by default (Many are normally run on a daily cron, not hourly). If you look in SingularityExecutorTaskLogManager.writeLogrotateFile you'll notice that we additionally write an hourly cron for the specific logrotate conf file that needs it. We will likely need an additional check in that method to see if an additional file has an hourly rotate and possibly a separate cron file if so (the current cron uses the -f option to support hourly and force a logrotate run for those versions that do not support it)

Copy link
Member

Choose a reason for hiding this comment

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

Additional note, enough OSs likely support hourly logrotate now that we could do away with this extra check if we verify that it still works as intended

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have discussed this at length and determined that it's ok to put the logrotate config files at /etc/logrotate.d/hourly. The Mesos slave running our SingularityExecutor needs to support hourly cron jobs at this directory. We have already internally updated our puppet deploy to enforce this config on mesos slaves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have decided to support older logrotate versions and added code to Singularity to force a rotation hourly using the -f flag and a cron schedule. This implementation writes config files to two directories:

  • /etc/logrotate.d
  • /etc/logrotate.d/hourly

dateformat -{{{ dateformat }}}{{#if extension}}.{{{ extension}}}{{/if}}
missingok
{{#if useFileAttributes}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package com.hubspot.singularity.executor.config;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.validation.Validator;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.hubspot.singularity.executor.SingularityExecutorLogrotateFrequency;
import com.hubspot.singularity.executor.models.LogrotateAdditionalFile;
import com.hubspot.singularity.executor.models.LogrotateTemplateContext;
import com.hubspot.singularity.runner.base.config.SingularityRunnerBaseModule;
import com.hubspot.singularity.runner.base.config.SingularityRunnerConfigurationProvider;

Expand Down Expand Up @@ -62,4 +70,34 @@ private SingularityExecutorConfiguration loadConfig(String file) {
}
}

@Test
public void itOverridesGlobalLogrotateFrequency() throws Exception {
Handlebars handlebars = new Handlebars();
Template template = handlebars.compile(SingularityExecutorModule.LOGROTATE_TEMPLATE);

LogrotateTemplateContext context = mock(LogrotateTemplateContext.class);

doReturn(SingularityExecutorLogrotateFrequency.WEEKLY.getLogrotateValue()).when(context).getLogrotateFrequency();

List<LogrotateAdditionalFile> testExtraFiles = new ArrayList<>();
testExtraFiles.add(new LogrotateAdditionalFile("/tmp/testfile.txt", "txt", "%Y%m%d",
Optional.of(SingularityExecutorLogrotateFrequency.DAILY)));

doReturn(testExtraFiles).when(context).getExtrasFiles();

// This sample output template, when copied into a staged Mesos slave and run with `logrotate -d <configFileName>`
// confirms that a testfile.txt at the /tmp/testfile.txt will be cycled daily instead of weekly
String text = template.apply(context);

// Assert that our config has both weekly and daily scopes, and that daily occurs second (thus overrides weekly
// in the /tmp/testfile.txt YAML object).

// Admittedly, YAML serialization would be better, but given this code doesn't actually test much without
// a binary of `logrotate` to run against, it's not a big deal.
assertThat(text.contains("weekly")).isTrue();
assertThat(text.contains("daily")).isTrue();
assertThat(text.indexOf("daily")).isGreaterThan(text.indexOf("weekly"));

}

}