Add `spring.pid.fail-on-write-error` support
Update `ApplicationPidFileWriter` to support a 'fail on write error' properties which allows the user to exit the application if the PID file cannot be written. This commit also deprecates `spring.pidfile` in favor of `spring.pid.file` so that the new property can be added without overlap. Fixes gh-2764
This commit is contained in:
parent
c13ff96b78
commit
becced5f0b
|
|
@ -18,6 +18,9 @@ package org.springframework.boot.actuate.system;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
|
@ -36,11 +39,21 @@ import org.springframework.util.Assert;
|
|||
* An {@link ApplicationListener} that saves application PID into file. This application
|
||||
* listener will be triggered exactly once per JVM, and the file name can be overridden at
|
||||
* runtime with a System property or environment variable named "PIDFILE" (or "pidfile")
|
||||
* or using a {@code spring.pidfile} property in the Spring {@link Environment}.
|
||||
* or using a {@code spring.pid.file} property in the Spring {@link Environment}.
|
||||
* <p>
|
||||
* If PID file can not be created no exception is reported. This behavior can be changed
|
||||
* by assigning {@code true} to System property or environment variable named
|
||||
* {@code PID_FAIL_ON_WRITE_ERROR} (or "pid_fail_on_write_error") or to
|
||||
* {@code spring.pid.fail-on-write-error} property in the Spring {@link Environment}.
|
||||
* <p>
|
||||
* Note: access to the Spring {@link Environment} is only possible when the
|
||||
* {@link #setTriggerEventType(Class) triggerEventType} is set to
|
||||
* {@link ApplicationEnvironmentPreparedEvent} or {@link ApplicationPreparedEvent}.
|
||||
*
|
||||
* @author Jakub Kubrynski
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Tomasz Przybyla
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ApplicationPidFileWriter implements
|
||||
|
|
@ -50,9 +63,22 @@ public class ApplicationPidFileWriter implements
|
|||
|
||||
private static final String DEFAULT_FILE_NAME = "application.pid";
|
||||
|
||||
private static final String[] SYSTEM_PROPERTY_VARIABLES = { "PIDFILE", "pidfile" };
|
||||
private static final List<Property> FILE_PROPERTIES;
|
||||
static {
|
||||
List<Property> properties = new ArrayList<Property>();
|
||||
properties.add(new SpringProperty("spring.pid.", "file"));
|
||||
properties.add(new SpringProperty("spring.", "pidfile"));
|
||||
properties.add(new SystemProperty("PIDFILE"));
|
||||
FILE_PROPERTIES = Collections.unmodifiableList(properties);
|
||||
}
|
||||
|
||||
private static final String SPRING_PROPERTY = "spring.pidfile";
|
||||
private static final List<Property> FAIL_ON_WRITE_ERROR_PROPERTIES;
|
||||
static {
|
||||
List<Property> properties = new ArrayList<Property>();
|
||||
properties.add(new SpringProperty("spring.pid.", "fail-on-write-error"));
|
||||
properties.add(new SystemProperty("PID_FAIL_ON_WRITE_ERROR"));
|
||||
FAIL_ON_WRITE_ERROR_PROPERTIES = Collections.unmodifiableList(properties);
|
||||
}
|
||||
|
||||
private static final AtomicBoolean created = new AtomicBoolean(false);
|
||||
|
||||
|
|
@ -108,7 +134,12 @@ public class ApplicationPidFileWriter implements
|
|||
writePidFile(event);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.warn(String.format("Cannot create pid file %s", this.file), ex);
|
||||
String message = String
|
||||
.format("Cannot create pid file %s", this.file);
|
||||
if (failOnWriteError(event)) {
|
||||
throw new IllegalStateException(message, ex);
|
||||
}
|
||||
logger.warn(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,31 +147,25 @@ public class ApplicationPidFileWriter implements
|
|||
|
||||
private void writePidFile(SpringApplicationEvent event) throws IOException {
|
||||
File pidFile = this.file;
|
||||
String override = SystemProperties.get(SYSTEM_PROPERTY_VARIABLES);
|
||||
String override = getProperty(event, FILE_PROPERTIES);
|
||||
if (override != null) {
|
||||
pidFile = new File(override);
|
||||
}
|
||||
else {
|
||||
Environment environment = getEnvironment(event);
|
||||
if (environment != null) {
|
||||
override = new RelaxedPropertyResolver(environment)
|
||||
.getProperty(SPRING_PROPERTY);
|
||||
if (override != null) {
|
||||
pidFile = new File(override);
|
||||
}
|
||||
}
|
||||
}
|
||||
new ApplicationPid().write(pidFile);
|
||||
pidFile.deleteOnExit();
|
||||
}
|
||||
|
||||
private Environment getEnvironment(SpringApplicationEvent event) {
|
||||
if (event instanceof ApplicationEnvironmentPreparedEvent) {
|
||||
return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
|
||||
}
|
||||
if (event instanceof ApplicationPreparedEvent) {
|
||||
return ((ApplicationPreparedEvent) event).getApplicationContext()
|
||||
.getEnvironment();
|
||||
private boolean failOnWriteError(SpringApplicationEvent event) {
|
||||
String value = getProperty(event, FAIL_ON_WRITE_ERROR_PROPERTIES);
|
||||
return (value == null ? false : Boolean.parseBoolean(value));
|
||||
}
|
||||
|
||||
private String getProperty(SpringApplicationEvent event, List<Property> candidates) {
|
||||
for (Property candidate : candidates) {
|
||||
String value = candidate.getValue(event);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -160,4 +185,69 @@ public class ApplicationPidFileWriter implements
|
|||
static void reset() {
|
||||
created.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to a property value.
|
||||
*/
|
||||
private static interface Property {
|
||||
|
||||
String getValue(SpringApplicationEvent event);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Property} obtained from Spring's {@link Environment}.
|
||||
*/
|
||||
private static class SpringProperty implements Property {
|
||||
|
||||
private final String prexfix;
|
||||
|
||||
private final String key;
|
||||
|
||||
public SpringProperty(String prefix, String key) {
|
||||
this.prexfix = prefix;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(SpringApplicationEvent event) {
|
||||
Environment environment = getEnvironment(event);
|
||||
if (environment == null) {
|
||||
return null;
|
||||
}
|
||||
return new RelaxedPropertyResolver(environment, this.prexfix)
|
||||
.getProperty(this.key);
|
||||
}
|
||||
|
||||
private Environment getEnvironment(SpringApplicationEvent event) {
|
||||
if (event instanceof ApplicationEnvironmentPreparedEvent) {
|
||||
return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
|
||||
}
|
||||
if (event instanceof ApplicationPreparedEvent) {
|
||||
return ((ApplicationPreparedEvent) event).getApplicationContext()
|
||||
.getEnvironment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Property} obtained from {@link SystemProperties}.
|
||||
*/
|
||||
private static class SystemProperty implements Property {
|
||||
|
||||
private final String[] properties;
|
||||
|
||||
public SystemProperty(String name) {
|
||||
this.properties = new String[] { name.toUpperCase(), name.toLowerCase() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(SpringApplicationEvent event) {
|
||||
return SystemProperties.get(this.properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.boot.actuate.system;
|
||||
|
||||
/**
|
||||
* Access to system properties.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class SystemProperties {
|
||||
|
|
|
|||
|
|
@ -87,9 +87,22 @@
|
|||
"type": "java.lang.String",
|
||||
"description": "Resource reference to a generated git info properties file."
|
||||
},
|
||||
{
|
||||
"name": "spring.pid.file",
|
||||
"type": "java.lang.String",
|
||||
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
|
||||
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||
}
|
||||
{
|
||||
"name": "spring.pid.fail-on-write-error",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Fail if ApplicationPidFileWriter is used but it cannot write the PID file.",
|
||||
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||
}
|
||||
{
|
||||
"name": "spring.pidfile",
|
||||
"type": "java.lang.String",
|
||||
"deprecated:" true,
|
||||
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
|
||||
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2010-2014 the original author or authors.
|
||||
* Copyright 2010-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -23,11 +23,13 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.boot.context.event.SpringApplicationEvent;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
|
@ -46,6 +48,7 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Jakub Kubrynski
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Tomasz Przybyla
|
||||
*/
|
||||
public class ApplicationPidFileWriterTests {
|
||||
|
||||
|
|
@ -56,10 +59,14 @@ public class ApplicationPidFileWriterTests {
|
|||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void resetListener() {
|
||||
System.clearProperty("PIDFILE");
|
||||
System.clearProperty("PID_FAIL_ON_WRITE_ERROR");
|
||||
ApplicationPidFileWriter.reset();
|
||||
}
|
||||
|
||||
|
|
@ -85,14 +92,8 @@ public class ApplicationPidFileWriterTests {
|
|||
@Test
|
||||
public void overridePidFileWithSpring() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty("spring.pidfile", file.getAbsolutePath());
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
given(context.getEnvironment()).willReturn(environment);
|
||||
ApplicationPreparedEvent event = new ApplicationPreparedEvent(
|
||||
new SpringApplication(), new String[] {}, context);
|
||||
SpringApplicationEvent event = createPreparedEvent("spring.pidfile",
|
||||
file.getAbsolutePath());
|
||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
||||
listener.onApplicationEvent(event);
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
||||
|
|
@ -101,12 +102,8 @@ public class ApplicationPidFileWriterTests {
|
|||
@Test
|
||||
public void differentEventTypes() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty("spring.pidfile", file.getAbsolutePath());
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(
|
||||
new SpringApplication(), new String[] {}, environment);
|
||||
SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pidfile",
|
||||
file.getAbsolutePath());
|
||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
||||
listener.onApplicationEvent(event);
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
|
||||
|
|
@ -125,4 +122,64 @@ public class ApplicationPidFileWriterTests {
|
|||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void continueWhenPidFileIsReadOnly() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
file.setReadOnly();
|
||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||
listener.onApplicationEvent(EVENT);
|
||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwWhenPidFileIsReadOnly() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
file.setReadOnly();
|
||||
System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true");
|
||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||
this.exception.expect(IllegalStateException.class);
|
||||
this.exception.expectMessage("Cannot create pid file");
|
||||
listener.onApplicationEvent(EVENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwWhenPidFileIsReadOnlyWithSpring() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
file.setReadOnly();
|
||||
SpringApplicationEvent event = createPreparedEvent(
|
||||
"spring.pid.fail-on-write-error", "true");
|
||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||
this.exception.expect(IllegalStateException.class);
|
||||
this.exception.expectMessage("Cannot create pid file");
|
||||
listener.onApplicationEvent(event);
|
||||
}
|
||||
|
||||
private SpringApplicationEvent createEnvironmentPreparedEvent(String propName,
|
||||
String propValue) {
|
||||
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
|
||||
return new ApplicationEnvironmentPreparedEvent(new SpringApplication(),
|
||||
new String[] {}, environment);
|
||||
}
|
||||
|
||||
private SpringApplicationEvent createPreparedEvent(String propName, String propValue) {
|
||||
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
given(context.getEnvironment()).willReturn(environment);
|
||||
return new ApplicationPreparedEvent(new SpringApplication(), new String[] {},
|
||||
context);
|
||||
}
|
||||
|
||||
private ConfigurableEnvironment createEnvironment(String propName, String propValue) {
|
||||
MockPropertySource propertySource = mockPropertySource(propName, propValue);
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
return environment;
|
||||
}
|
||||
|
||||
private MockPropertySource mockPropertySource(String name, String value) {
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty(name, value);
|
||||
return propertySource;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -612,7 +612,8 @@ content into your application; rather pick only the properties that you need.
|
|||
management.security.sessions=stateless # session creating policy to use (always, never, if_required, stateless)
|
||||
|
||||
# PID FILE ({sc-spring-boot-actuator}/system/ApplicationPidFileWriter.{sc-ext}[ApplicationPidFileWriter])
|
||||
spring.pidfile= # Location of the PID file to write
|
||||
spring.pid.file= # Location of the PID file to write
|
||||
spring.pid.fail-on-write-error= # Fail if the PID file cannot be written
|
||||
|
||||
# ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses)
|
||||
endpoints.autoconfig.id=autoconfig
|
||||
|
|
|
|||
Loading…
Reference in New Issue